How do i construct a route without ViewContext in ASP.NET MVC? - asp.net-mvc

I want to create a route from a utility class that doesn't have access to a ViewContext.
Is this possible? There doesnt seem to be any equivalent of ViewContext.Current
I've tried fishing around in all the constructors for Routing and HttpContext but can't quite get to what I want.
This is what I'm looking for - although this doesn't work because RouteTable.Routes is of type RouteCollection and not RouteData. So close - yet so far :-)
RequestContext requestContext = new RequestContext(HttpContext.Current, RouteTable.Routes);
UrlHelper url = new UrlHelper(requestContext);
var urlString = url.RouteUrl(new {controller="DynamicImage", action="Button", text="Hello World"});
Note: RequestContest is of type System.Web.Routing.RequestContext and not HttpContext

Try this:
var httpContext = new HttpContextWrapper(HttpContext.Current);
var requestContext = new RequestContext(httpContext);
var urlHelper = new UrlHelper(requestContext, new RouteData()));
Hope this helps
UPDATED:
The previous is not correct (I've posted it from my memory). Try this instead (it works in one of my projects):
var httpContext = new HttpContextWrapper(HttpContext.Current);
var requestContext = new RequestContext(httpContext, new RouteData());
var urlHelper = new UrlHelper(requestContext);
new RouteData() is using just only for RequestContext initialization and new UrlHelper(requestContext) actually calls new UrlHelper(requestContext, RouteTable.Routes)

Related

MVC: HtmlHelper: Get action/controller values

My project has a HtmlHelper which generates links (with routeValues) which always direct back to the controller/action which 'spawned' them.
Is it possible to retrieve these values from within the HtmlHelper? i.e without supplying them explicitly. This would work fine....
var url = new UrlHelper(html.ViewContext.RequestContext);
anchorBuilder.MergeAttribute("href", url.Action("Details", routeValues));
...were it not for the fact that that action won't always be "Details".
Or this:
var controller = helper.ViewContext.RouteData.Values["Controller"].ToString();
var action = helper.ViewContext.RouteData.Values["Action"].ToString();
Try this:
var currentAction = html.ViewContext.RouteData.GetRequiredString("action");
var url = new UrlHelper(html.ViewContext.RequestContext);
anchorBuilder.MergeAttribute("href", url.Action(currentAction, routeValues));

Run a URL string through the ASP.NET MVC pipeline to get an ActionResult

I have a list of URLs that I obtained by querying Google Analytics data. I want to run each of these URLs through the MVC pipeline to get the ActionResult. The action result contains the view model from which I can extract some important information.
Based on the extensibility of MVC, I thought this would be easy. I thought I could mock up a HttpRequest using the string URL and pass it through the routing and controller. My end point would be invoking the action method which would return the ActionResult. I'm finding bits and pieces of what I need, but a lot of the methods are protected within the various classes and the documentation on them is pretty sparse.
I somehow want to reach in to the ControllerActionInvoker and get the result of the call to the protected function InvokeActionMethod.
First of all, Darin's answer got me started, but there's a lot more detail to the final solution, so I'm adding a separate answer. This one is complex, so bear with me.
There are 4 steps to getting the ViewResult from a URL:
Mock the RequestContext via the routing system (Darin's answer got me started on this).
Uri uri = new Uri(MyStringUrl);
var request = new HttpRequest(null, uri.Scheme + "://" + uri.Authority + uri.AbsolutePath, string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1));
var response = new HttpResponse(new StringWriter());
var context = new HttpContext(request, response);
var contextBase = new HttpContextWrapper(context);
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
// We shouldn't have to do this, but the way we are mocking the request doesn't seem to pass the querystring data through to the route data.
foreach (string key in request.QueryString.Keys)
{
if (!routeData.Values.ContainsKey(key))
{
routeData.Values.Add(key, request.QueryString[key]);
}
}
var requestContext = new System.Web.Routing.RequestContext(contextBase, routeData);
Subclass your controller. Add a public method that allows you to call the protected Execute(RequestContext) method.
public void MyExecute(System.Web.Routing.RequestContext requestContext)
{
this.Execute(requestContext);
}
In the same subclassed controller, Add a public event that hooks in to the protected OnActionExecuted event. This allows you to reach in a grab the ViewResult via the ActionExecutedContext.
public delegate void MyActionExecutedHandler(ActionExecutedContext filterContext);
public event MyActionExecutedHandler MyActionExecuted;
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (MyActionExecuted != null)
{
MyActionExecuted(filterContext);
}
}
Tie everything together by instantiating an instance of the new controller subclass, adding an event handler, and calling the new public execute method (passing in the mocked RequestContext). The event handler will give you access to the ViewResult.
using (MyCompany.Controllers.MyController c = new Controllers.MyController())
{
c.MyActionExecuted += GrabActionResult;
try
{
c.MyExecute(requestContext);
}
catch (Exception)
{
// Handle an exception.
}
}
and here's the event handler:
private void GrabActionResult(System.Web.Mvc.ActionExecutedContext context)
{
if (context.Result.GetType() == typeof(ViewResult))
{
ViewResult result = context.Result as ViewResult;
}
else if (context.Result.GetType() == typeof(RedirectToRouteResult))
{
// Handle.
}
else if (context.Result.GetType() == typeof(HttpNotFoundResult))
{
// Handle.
}
else
{
// Handle.
}
}
The difficulty here consists into parsing the url into its constituent controller and action. Here's how this could be done:
var url = "http://example.com/Home/Index";
var request = new HttpRequest(null, url, "");
var response = new HttpResponse(new StringWriter.Null);
var context = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var values = routeData.Values;
var controller = values["controller"];
var action = values["action"];
Now that you know the controller and the action you could use reflection to instantiate and execute it.
Try this:
object result = null;
Type controller = Type.GetType("MvcApplication4.Controllers.HomeController");
if (controller != null)
{
object controllerObj = Activator.CreateInstance(controller, null);
if (controller.GetMethod("ActionName") != null)
{
result = ((ViewResult)controller.GetMethod("ActionName").Invoke(controllerObj, null)).ViewData.Model;
}
}
I assumed normal routes are configured in the application and can be retrieved using regex or string operations. Following your discussion, I learned that you guys want to really follow through the MVC pipeline by digging into the framework by not using reflection or any hardcording techniques. However, I tried to search to minimize hardcoding by trying to match the url with the routes configured in the application by following this thread
How to determine if an arbitrary URL matches a defined route
Also, I came across other thread which creates httprequest to access routedata object but again reflection needs to be used for this.
String URL to RouteValueDictionary
Thanks Ben Mills, this got me started with my own problem. However I found that I didn't have to do 2, 3 or 4, by doing the following.
Uri uri = new Uri(MyStringUrl);
var absoluteUri = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath;
var query = string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1);
var request = new HttpRequest(null, absoluteUri, query);
Getting access to the string writer is important.
var sw = new StringWriter();
var response = new HttpResponse(sw);
var context = new HttpContext(request, response);
var contextBase = new HttpContextWrapper(context);
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
If we assign the RouteData to the request context we can use the MVC pipeline as intended.
request.RequestContext.RouteData = routeData;
var controllerName = routeData.GetRequiredString("controller");
var factory = ControllerBuilder.Current.GetControllerFactory();
var contoller = factory.CreateController(request.RequestContext, controllerName);
controller.Execute(request.RequestContext);
var viewResult = sw.ToString(); // this is our view result.
factory.ReleaseController(controller);
sw.Dispose();
I hope this helps someone else wanting to achieve similar things.

Testing custom ModelBinder against HTTP context in ASP.NET MVC (1.0)

I'm trying to unit test a custom model binder - specifically, I want to see how it responds to various (possibly conflicting) values being submitted in the Request.Form and Request.QueryString collections - i.e. if I submit one value in the form and another in the querystring (yeah, yeah, I know, this is evil, but I want test coverage in case it happens) I can validate exactly which one will be bound to the model.
In order to do this, I'd like to mock/fake the HTTP context, and then invoke the model binder and see what is actually returned. I've seen several posts about testing ModelBinders, but all of them use a custom ValueProvider, whereas I actually want to test the way MVC is interacting with Form/Request collections.
Any ideas how I can mock these collections and then cause my model binder to use the 'default' ValueProvider based on this mocked HTTP context in my units tests? This is on ASP.NET MVC 1.0. Thanks.
Nailed it - the solution is to mock the ControllerContext, and then construct a new System.Web.Mvc.ValueProviderDictionary and pass your mocked controller context into the constructor, as follows:
[Test]
public void WorkFolder_Id_Is_Parsed_From_QueryString() {
var fakeControllerContext = GetControllerContext(null, "folder=10");
var bindingContext = new ModelBindingContext() {
ValueProvider = new System.Web.Mvc.ValueProviderDictionary(fakeControllerContext),
ModelName = "menu",
FallbackToEmptyPrefix = true
};
var binder = new RenewalMenuPostModelBinder();
var model = binder.BindModel(fakeControllerContext, bindingContext) as RenewalMenuPostModel;
Assert.That(model is RenewalMenuPostModel);
Assert.That(model.WorkFolderId.HasValue);
Assert.That(model.WorkFolderId.Value == 10);
}
private static ControllerContext GetControllerContext(NameValueCollection form, string queryString) {
Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
mockRequest.Expect(r => r.Form).Returns(form);
var queryStringCollection = HttpUtility.ParseQueryString(queryString);
mockRequest.Expect(r => r.QueryString).Returns(queryStringCollection);
Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>();
mockHttpContext.Expect(c => c.Request).Returns(mockRequest.Object);
return new ControllerContext(mockHttpContext.Object, new RouteData(), new Mock<ControllerBase>().Object);
}

ASP.NET MVC: Mock controller.Url.Action

Urls for menus in my ASP.NET MVC apps are generated from controller/actions. So, they call
controller.Url.Action(action, controller)
Now, how do I make this work in unit tests?
I use MVCContrib successfully with
var controller = new TestControllerBuilder().CreateController<OrdersController>();
but whatever I try to do with it I get controller.Url.Action(action, controller) failing with NullReferenceException because Url == null.
Update: it's not about how to intercept HttpContext. I did this in several ways, using MVCContrib, Scott Hanselman's example of faking, and also the one from http://stephenwalther.com/blog/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context.aspx. This doesn't help me because I need to know WHAT values to fake... is it ApplicationPath? How do I set it up? Does it need to match the called controller/action? That is, how do Url.Action works and how do I satisfy it?
Also, I know I can do IUrlActionAbstraction and go with it... but I'm not sure I want to do this. After all, I have MVCContrib/Mock full power and why do I need another abstraction.
A cleaner way to do this is just use Moq(or any other framework you like) to Mock UrlHelper itself
var controller = new OrdersController();
var UrlHelperMock = new Mock<UrlHelper>();
controller.Url = UrlHelperMock.Object;
UrlHelperMock.Setup(x => x.Action("Action", "Controller", new {parem = "test"})).Returns("testUrl");
var url = controller.Url.Action("Action", "Controller", new {parem = "test"});
assert.areEqual("/Controller/Action/?parem=test",url);
clean and simple.
Here's how you could mock UrlHelper using MvcContrib's TestControllerBuilder:
var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
HomeController controller = CreateController<HomeController>();
controller.HttpContext.Response
.Stub(x => x.ApplyAppPathModifier("/Home/About"))
.Return("/Home/About");
controller.Url = new UrlHelper(
new RequestContext(
controller.HttpContext, new RouteData()
),
routes
);
var url = controller.Url.Action("About", "Home");
Assert.IsFalse(string.IsNullOrEmpty(url));
If you're using Moq (and not MvcContrib's TestControllerBuilder), you can mock out the context, similar to #DarianDimitrov's answer:
var controller = new OrdersController();
var context = new Mock<System.Web.HttpContextBase>().Object;
controller.Url = new UrlHelper(
new RequestContext(context, new RouteData()),
new RouteCollection()
);
This doesn't set the controller.HttpContext property, but it does allow Url.Action to execute (and return an empty string -- no mocking required).
Fake it easy works nicely:
var fakeUrlHelper = A.Fake<UrlHelper>();
controller.Url = fakeUrlHelper;
A.CallTo(() => fakeUrlHelper.Action(A<string>.Ignored, A<string>.Ignored))
.Returns("/Action/Controller");
Here is another way to solve the problem with NSubstitute. Hope, it helps someone.
// _accountController is the controller that we try to test
var urlHelper = Substitute.For<UrlHelper>();
urlHelper.Action(Arg.Any<string>(), Arg.Any<object>()).Returns("/test_Controller/test_action");
var context = Substitute.For<HttpContextBase>();
_accountController.Url = urlHelper;
_accountController.ControllerContext = new ControllerContext(context, new RouteData(), _accountController);

String URL to RouteValueDictionary

Is there as easy way to convert string URL to RouteValueDictionary collection? Some method like UrlToRouteValueDictionary(string url).
I need such method because I want to 'parse' URL according to my routes settings, modify some route values and using urlHelper.RouteUrl() generate string URL according to modified RouteValueDictionary collection.
Thanks.
Here is a solution that doesn't require mocking:
var request = new HttpRequest(null, "http://localhost:3333/Home/About", "testvalue=1");
var response = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
var values = routeData.Values;
// The following should be true for initial version of mvc app.
values["controller"] == "Home"
values["action"] == "Index"
Hope this helps.
You would need to create a mocked HttpContext as routes constrains requires it.
Here is an example that I use to unit test my routes (it was copied from Pro ASP.Net MVC framework):
RouteCollection routeConfig = new RouteCollection();
MvcApplication.RegisterRoutes(routeConfig);
var mockHttpContext = new MockedHttpContext(url);
RouteData routeData = routeConfig.GetRouteData(mockHttpContext.Object);
// routeData.Values is an instance of RouteValueDictionary
//...
I wouldn't rely on RouteTable.Routes.GetRouteData from previous examples because in that case you risk missing some values (for example if your query string parameters don't fully fit any of registered mapping routes). Also, my version doesn't require mocking a bunch of request/response/context objects.
public static RouteValueDictionary UrlToRouteValueDictionary(string url)
{
int queryPos = url.IndexOf('?');
if (queryPos != -1)
{
string queryString = url.Substring(queryPos + 1);
var valuesCollection = HttpUtility.ParseQueryString(queryString);
return new RouteValueDictionary(valuesCollection.AllKeys.ToDictionary(k => k, k => (object)valuesCollection[k]));
}
return new RouteValueDictionary();
}
Here is a solution that doesn't require instantiating a bunch of new classes.
var httpContext = context.Get<System.Web.HttpContextWrapper>("System.Web.HttpContextBase");
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(httpContext);
var values = routeData.Values;
var controller = values["controller"];
var action = values["action"];
The owin context contains an environment that includes the HttpContext.

Resources