Test ASP.NET MVC routes using MVC Contrib - asp.net-mvc

I'm trying to set up Route mapping tests using MVC Contrib as described in Test ASP.NET MVC routes using MVC Contrib
The tests compile and execute, but they always fail with the message "The URL did not match any route."
I set up another test to try to get an idea of what the problem is:
Public Sub TestIndexRoute()
Dim routes = New RouteCollection
myMvcApp.MvcApplication.RegisterRoutes(routes)
Assert.That(routes.Count > 0)
Assert.NotNull(routes("Default"), "Default route not found.")
Dim routeData = RouteTestingExtensions.Route("~/Author")
Assert.NotNull(routeData, "routeData is Nothing.")
Assert.That(routeData.Values("controller") = "Author")
End Sub
That test fails on Assert.NotNull(routeData, "routeData is Nothing."), so I know that there must be some problem with the MVCContrib code that is trying to access my app's RouteCollection.
From the blog post:
It also assumes you set your routes in the ASP.NET MVC RouteCollection object.
How do I confirm that I'm doing that? I'm using routes.MapRoute within MvcApplication.RegisterRoutes method in the Global.asax code behind. Is there something else to do to set this up properly?
Edit: I should probably mention that I'm new to unit testing. I've been putting off learning it for too long and this seemed like as good a place to start as any.

Try:
MvcApplication.RegisterRoutes(RouteTable.Routes);
instead of:
Dim routes = New RouteCollection
myMvcApp.MvcApplication.RegisterRoutes(routes)
See RouteTestingExtensions, line 43

Related

Area webapi route not being resolved

am trying to test the web api routes, that exists inisde my administration area.
This is the the route definition in the area
[HttpPut]
[Route("timezone/put/{timezone}", Name = "PutTimeZone")]
[AllowAnonymous]
[ResponseType(typeof(void))]
public IHttpActionResult PutTimeZone(string timezone)
{
/*Action body*/
}
I have a route prefix like so [RoutePrefix("admins/misc")]
Here is my route test
const string route = "/admins/misc/timezone/put/-120";
RouteAssert.HasApiRoute(_httpConfiguration, route, HttpMethod.Put);
_httpConfiguration.ShouldMap(HttpMethod.Put, route)
.To<MiscApiController>(HttpMethod.Put, x => x.PutTimeZone("-120"));
When I run the test, I get MvcRouteTester.Assertions.AssertionException : Expected 'Administration', got no value for 'area' at url '/admins/misc/timezone/put/-120'.
I read in the route tester wiki page
If you are using MVC areas, then as long as you use the standard
layout of namespaces, the area name will be extracted from your
controller type name and tested against the area chosen by the route.
e.g. if your controller's fully qualified type name is
MyWebApp.Areas.Blog.CommentController then the expected area name will
be "Blog".
But frankly, it doesn't give me any clue as to what to do so as to make my test pass. My question is what am I missing?
Try replacing [RoutePrefix("admins/misc")] with [RouteArea("Administration",AreaPrefix = "admins/misc")] or combining both together with [RouteArea("Administration",AreaPrefix = "admins"),RoutePrefix("misc")].
Edit:
I downloaded the source for the MvcRouteTester library and tried to debug it using almost the same example as you provided and it looks like there is an issue with the library itself.
Specifics: one method Common.Verifier.VerifyExpectations asserts that the expected area expected.Area matches route's actual area actual.Area, but there is no previous code that sets the actual area property to any value (although it does set Controller and Action properties in ApiRoute.Generator.ReadRequestProperties).
So I suppose that at its current state (at the time of this post) the library simply does not support areas for WebApi. I suggest that you open an issue on the MvcRouteTester github page with a link to this post.

Deploying un-compiled ASP.NET MVC Razor application

The question "https://stackoverflow.com/questions/16397785/add-pages-to-mvc-deployed-website" seems to relate closely to the question I want to ask. But it my case, I have not yet developed an application, but I want to develop it in such a way that it can be customized by users after it is deployed by adding files to the deployed directory. What would be the advisable way of doing this? Specifically, I want users to be able to define custom pages, possibly replace existing pages or add controls to existing pages, and possibly define custom WebAPI functions to retrieve custom data. I tried adding .vbhtml files to the Views directory as described, but ran into the same problem described in the linked question.
Razor views are compiled at runtime, so you always deploy them un-compiled.
What you want is simply Razor pages, so can add them to any directory (not Views) and access them without the file extension (e.g. /Foo/Bar instead of /Foo/Bar.vbhtml).
I don't think I would recommend using the filesystem for this. I think you should save the razor code to the database and then, when you wish to parse it, you may do so according to this Using RazorEngine to parse Razor templates concurrently
Here is another example:
var model = new { Name = "Test" };
var template = "Hello #Model.Name";
var result = Razor.Parse(template, model);
Code taken from Using Razor engine on strings - not views
Edit to answer your questions:
The razor code would be stored in the database along with whatever controller you wanted to run it. When you retrieve the razor code from the database, you would also know the controller, then you could redirect to that controller, sending whatever model you want, to the razor code, and then parse it as shown above. Make sense?
The routing configuration can be customized to identify a URL pattern that identifies all requests for customized code and route them through a common controller that can then load arbitrary views based on information provided in the URL. Notice the route with the name Custom below, and how it always uses the Index action of a controller named Custom, but introduces a new parameter customization.
Public Class RouteConfig
Public Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.MapRoute( _
name:="Custom", _
url:="custom/{customization}/{id}", _
defaults:=New With {.controller = "Custom", .action = "Index", .id = UrlParameter.Optional} _
)
routes.MapRoute( _
name:="Default", _
url:="{controller}/{action}/{id}", _
defaults:=New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional} _
)
End Sub
End Class
Once this is set up, add a controller called CustomController to the project that simply passes through to a view determined by the customization parameter:
Public Class CustomController
Inherits System.Web.Mvc.Controller
'
' GET: /Custom
Function Index(customization As String) As ActionResult
Return View(customization)
End Function
End Class
Publish this code and add a Custom directory under the Views directory in the deployed location. Now you can put any .vbhtml file in the Custom folder and refer to it with a URL like http://localhost/MyApplication/Custom/MyView, which will load MyView.vbhtml from the Views\Custom directory.
If the main intent is to provide extensibility, then there is a simpler answer. ASP.Net will probe all assemblies in the bin directory of the deployed web application and pick up all controllers and models in compiled assemblies there, as well as all un-compiled views in the Views directory. With some effort I was able to determine a minimal set of files necessary to create an independent template project that could be used by people who would develop and deploy custom code into the running (deployed) web application. The details are provided as an answer to a more relevant question I discovered on this topic because it was not straightforward to get Intellisense and other ASP.Net MVC4 apsects of this template project working. See https://stackoverflow.com/a/21122377/78162
Since Visual Studio Express is now available for free, my hope is that such a template project could be loaded as a starting point for developing customizations to another ASP.Net MVC4 application. My limited testing indicates this will work for both the UI layer (as demonstrated in previous link) and the API layers (as discussed at https://stackoverflow.com/a/21028825/78162).

How to register MVC4 portable areas and routes in nUnit tests?

My MVC4 solution contains three projects:
Web, an MVC 4 web application
MyAccount, a portable area
Tests, a class library that leverages nUnit (and moq)
Web's global.asax defines some routes, as does MyAccount (in its implementation of PortableAreaRegistration).
In my nUnit tests, I need to get the entire solution's set of routes. I can get the routes defined in Web using code like:
if (RouteTable.Routes.Count == 0)
{
RouteRegistrationConfigurator.Configure();
MvcApplication.RegisterRoutes(RouteTable.Routes);
}
When I try to expand that to include portable areas using this:
if (RouteTable.Routes.Count == 0)
{
var areaRegistration = new MyAccountAreaRegistration();
var areaRegistrationContext = new AreaRegistrationContext(areaRegistration.AreaName, RouteTable.Routes);
areaRegistration.RegisterArea(areaRegistrationContext);
RouteRegistrationConfigurator.Configure();
MvcApplication.RegisterRoutes(RouteTable.Routes);
}
I get an InvalidOperationException: "Operation is not valid due to the current state of the object." on the RegisterArea() call.
Putting this code in either the test itself or in the TestFixtureSetUp method makes no difference to the error.
The AreaRegistrationContext constructor has an optional third parameter that is an object state. By default, that's null. Giving it an empty object (new {} or new object {}) doesn't fix the error. I've not been able to find any documentation as to what that state object can or should be.
Using an AreaRegistration.RegisterAllAreas() call instead of registering the specific area results in the error message "InvalidOperationException : This method cannot be called during the application's pre-start initialization phase."
I see examples of these area registration lines being usable in MVC2 projects without this error. Is something different about MVC4 that changes how I should be setting this up?

Can you split an MVC Area across two projects?

I have an Asp.Net MVC 3 website written in VB.Net. We want to migrate to C#, the plan being to write all new code in C#, and rewriting old code as and when it's amended.
I created a new C# MVC 3 project in the solution. Within this project I am using Razor Generator to help compile the views into the resulting assembly. I reference the C# project from the VB one. The VB project runs as the main site.
This set up works 90% beautifully. However, if an Area already exists in the VB project, I can't seem to extend it in the C# project. It appears that the whole Area has to exist in either one project or the other.
Does anyone know if it is possible to serve 1 area from 2 projects?
I had to work around this by creating a route in the area registration of the VB.Net project.
The Area Registration file in the C# project needed to be removed, and the route in the VB.Net project uses a slightly different URL pattern. When creating the new route the Namespace that the C# controllers reside in need to be set. The new route also needs to be declared before the default route for the area.
It is entirely possible, and what you are doing is correct. I would surmise that your routes are not set up correctly. You will have to specify the namespaces in the routing in your VB project.
In your AreaRegistration code:
Public Overrides Sub RegisterArea(ByVal context As AreaRegistrationContext)
context.MapRoute(
"Users_default",
"Users/{controller}/{action}/{id}",
New With {.action = "Index", .id = UrlParameter.Optional},
New String() {"MyCompany.MyAmazingCSProject.Areas.Users.Controllers",
Me.GetType().Namespace}
)
End Sub
Remember that VB namespaces have the added complication of root namespaces, which work differently to the Default namespace of C# projects, so if you're working with both, you need to be consistent.
i.e.
A VB project with a root namespace of MyCompany.MyAmazingVBProject has this namespace in code:
Namespace Areas.Users.Controllers
which maps to MyCompany.MyAmazingVBProject.Areas.Users.Controllers, whereas the equivalent namespace in C# would have this code:
namespace MyCompany.MyAmazingCSProject.Areas.Users.Controllers { ... }

Legacy URL rewriting with query string parameters

I've looked at ASP.Net MVC routing legacy URLs passing querystring Ids to controller actions and several other similar posts for legacy URL routing, but I can't get past the error "The RouteData must contain an item named 'controller' with a non-empty string value." Looking this up on line didn't give me any hints to solve my problem.
I've implemented the Legacy routing class described in the link above, and this is what I've defined in the routing table:
routes.Add(
"Legacy",
new LegacyRoute("fooref.aspx",
"FooRef",
new LegacyRouteHandler())
);
routes.MapRoute(
"FooRef",
"{controller}/{action}",
new
{
controller = "Home",
action = "Index",
foo_id = UrlParameter.Optional,
bar_id = UrlParameter.Optional
}
);
When I use Phil Haack's route debugger, it indicates that fooref.aspx has a match, but when I turn the route debugger off, I get the error above. If I reverse the statement order, I get "Resource not found" for /ctprefer.aspx, which makes sense -- so it appears to be finding that as a valid route when put in the other order.
Where do I need to declare this missing controller reference?
Have routing requirements changed for ASP.NET MVC 2 RTM?
The solution to this is to use an IHttpHandler directly, rather than an MVCHandler. I have posted code that works with MVC 3 to my blog: http://www.olsonsoft.com/blogs/stefanolson/post/Handling-Legacy-Urls-in-AspNet-MVC-3.aspx
...Stefan
You might want to take a look at URL Rewrite module for IIS. It can be used to translate legacy URL to your new MVC URLs without 'polluting' your app with legacy routes.
Don't know if it will fit your solution but it's worth having an alternative.

Resources