How to register MVC4 portable areas and routes in nUnit tests? - asp.net-mvc

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?

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.

MVCContrib portable areas not working from HtmlExtensions, MVC 3

I just implemented MVCContrib's Portable Area feature and it works fine. I can open it using:
http://localhost/projectname/portableAreaName, but this portable area is not working if i render it using the HtmlHelper extension method like this:
public static void RenderHtmlWidget(this HtmlHelper Html)
{
Html.RenderAction("Index", "HtmlWidget", new {area = "HtmlWidget"});
}
And calling the helper method in the view as such:
#using Project.Widgets.HtmlWidget;
#{Html.RenderHtmlWidget();}
I'm getting an error: The view 'Index' or its master was not found or no view engine supports the searched locations. In the possible location list there are no ~/areas/... defined.
But I can render my HtmlWidget successfully with this the same line of code in the view:
#{Html.RenderAction("Index", "HtmlWidget", new { area = "HtmlWidget" });}
What am I doing wrong and how should I use the HtmlHelper extensions correctly with the MVCContrib portable areas feature?
There are a few things that may be causing this.
In the calling/parent project where you use the helper method to invoke your portable area, do you have a Web.config file in the /Areas/ folder? If not, you must copy the Web.config found in the /Views/ folder of the same project, and simply place the new copy in the /Areas/ folder as well.
In the Registration class file in your portable area project, after you call MapRoute in the "RegisterArea" method, are you calling "RegisterAreaEmbeddedResources();"?
Is each view in your portable area project made to be an embedded resource as opposed to content? Select a View in the Solution Explorer and hit F4, "Build Action" should be set to "Embedded Resource", but it defaults to "Content"
You also need to make sure that both the Portable project and the consuming project reference the same version of MvcContrib, but that they also utilize the same version of ASP.NET MVC. If your area is referenced in multiple projects, each based off of a different version of MVC (not likely, but possible depending on the situation), your area must use whatever version of MVC the consuming project uses.
I'd also suggest using Phil Haack's .NET Routing Debugger - its a single DLL file that you reference in the consuming application and add a single line to your ApplicationStart() in your Global.asax.cs. This becomes incredibly helpful in determining if your portable area is being correctly registered with the base project - and helps you cut to the chase.

Test ASP.NET MVC routes using MVC Contrib

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

Custom Controller Factory, Dependency Injection / Structuremap problems with ASP.NET MVC

I recently tried to implement dependency injection using StructureMap. I managed to follow the example all the way but I'm encountering a thrown exception every time I try to run the application. Here's some code snippets from my controller factory.
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
if (controllerType == null)
throw new ArgumentNullException("controllerType");
return ObjectFactory.GetInstance(controllerType) as Controller;
}
}
My Global.asax calls a static BootStrapper class which registers all my dependencies but it looks like the StructureMapControllerFactory's GetControllerInstance is getting called twice. The first time it's called, a proper controllerType is passed in but it's always null on the second calling.
The first time GetControllerInstance is called, the type is set to HomeController which is correct. The code the fires in HomeController and it returns the View(). On the return of the View(), the Page_Load event is called on the aspx file. After stepping through that, it arrives at a line:
httpHandler.ProcessRequest(HttpContext.Current);
That's where the GetControllerInstance is called the second time.
Here's my Global.asax bits which may be relevant:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
Bootstrapper.ConfigureStructureMap();
ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
}
I'm very keen to get dependency injection working and would be most appreciative if anyone can help me out. :)
UPDATE
The reason this is happening is that when an image is specified in a css file, and that image is missing the routing framework tries to map the url to a controller. Ignoring the routes as listed below will prevent the error from happening, but I decided not to implement it because it's a nice way of being notified that you have a missing resource.
You can see a detailed explanation along with examples of setting up dependency injection using Structure Map here.
I was having the same issue and I think I figured it out. For whatever reason whenever you try to provide your own Controller Factory you need to add a couple of additional IgnoreRoute definitions. The first thing I would do is add this line to your GetControllerInstance method:
if(controllerType == null)
return base.GetControllerInstance(controllerType)
This will at least give you more information in the error message as to what the request is that is causing the controllerType to be null. In my particular case I had a background image in my css file set like this:
background: url(img/logo.gif);
So what happens is the routing is looking for a controller called "img" and an action that takes logo.gif or something along those lines. That's obviously not the correct behavior.
After some digging I realized I needed to add an IgnoreRoute definition to my Global.asax file that would ignore anything in the "content" directory of my site like this:
routes.IgnoreRoute("{Content}/{*pathInfo}");
I'm still not sure why you don't need to do this with the default ControllerFactory, but nonetheless it works.
You'll probably run into the same issue with favicon.ico. Particularly if you using google crhome. You'll need to add this as well:
routes.IgnoreRoute("{*favicon}", new {favicon=#"(.*/)?favicon.ico(/.*)?"})
I am guessing you are requesting a static file, such as a javascript file, that has not been added to your project or perhaps the reference to it has a typo. You should monitor your browsers web requests and look for web requests for that do not correctly resolve to a static file when they should. You can use firebug in FF or fiddler if you are using IE to do this.

Issues During ASP.NET MVC Upgrade from Preview 5 to Beta?

What issues or refactoring did you have to do when you upgraded from ASP.NET MVC Preview 5 to the newly released Beta version?
Issue number one: Yellow screen of death.
CS0234: The type or namespace name 'Mvc' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)
Solution: I removed all references in my project and re-added them, pointing to the assemblies in program files\asp.net\asp.net mvc beta\assemblies, but that didn't solve the problem.
I had a system.web.mvc dll in the gac (no idea how). Tried to delete it. Unable to; assembly is required by one or more applications. Had to find the assembly as described here and delete the registry entry. I was then able to remove the gac's version of system.web.mvc.
This STILL didn't fix the problem. I had to RE-ADD the references AGAIN. Now its working.
Just to be clear!!! The beta assemblies were dropped under Program Files, while an older version of System.Web.Mvc was in the GAC.
I'm about to do this myself. Here's the list of changes from the readme:
Changes Made Between CodePlex Preview 5 and Beta
Changed the default validation messages to be more end-user friendly.
Renamed CompositeViewEngine to AutoViewEngine.
Added a Url property to Controller of type UrlHelper. This makes it convenient to generate routing-based URLs from within a controller.
Added the ActionNameSelectorAttribute abstract base class, which serves as the base type for ActionNameAttribute. By inheriting from this base attribute class, you can create custom attributes that participate in action selection by name.
Added a new ReleaseView method to IViewEngine that allows custom view engines to be notified when a view is done rendering. This is useful for cleanup or for view-pooling scenarios.
Renamed the ControllerBuilder method DisposeController to ReleaseController to fit with the pattern that is established for view engines.
Removed most of the methods on the HtmlHelper class, converting them to extension methods of the HtmlHelper class instead. These methods exist in a new namespace (System.Web.Mvc.Html). If you are migrating from Preview 5, you must add the following element to the namespaces section of the Web.config file:
<add namespace="System.Web.Mvc.Html"/>
This makes it possible for you to completely replace our helper methods with your own.
Changed the default model binder (DefaultModelBinder) to handle complex types. The IModelBinder interface has also been changed to accept a single parameter of type ModelBindingContext.
Added a new HttpVerbs enumeration that contains the most commonly used HTTP verbs (GET, POST, PUT, DELETE, HEAD). Also added a constructor overload to AcceptVerbsAttribute that accepts the enumeration. The enumerated values can be combined. Because it is possible to respond to HTTP verbs that are not included in the enumeration, the AcceptVerbsAttribute retains the constructor that accepts an array of strings as a parameter. For example, the following snippet shows an action method that can respond to both POST and PUT requests.
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Put)]
public ActionResult Update() {...
}
Modified the RadioButton helper method to ensure that every overload accepts a value. Because radio buttons are used to specify a choice from a set of possible values, specifying a value for a radio button is necessary.
Made modifications and fixes to the default project template. This includes moving script files to a new Scripts folder. The default template uses the ModelState class to report validation errors.
Changed action-method selection. If two action methods match a request, but only one of those has an attribute that derives from ActionMethodSelectorAttribute that matches the request, that action is invoked. In earlier releases, this scenario resulted in an exception.
For example, the following two action methods are in the same controller:
public ActionResult Edit() {
//...
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(FormCollection form) {
//...
}
In Preview 5, a POST request for the Edit action would cause an exception, because two methods match the request. In the Beta, precedence is given to the method that matches the current request via the AcceptVerb attribute. In this example, the first method will handle any non-POST requests for the Edit action.
Added an overload for the ViewDataDictionary.Eval method that accepts a format string.
Removed the ViewName property from the ViewContext class.
Added an IValueProvider interface for value providers, along with a default implementation, DefaultValueProvider. Value providers supply values that are used by the model binders when binding to a model object. The UpdateModel method of the Controller class has been updated to allow you to specify a custom value provider.
I experienced the same problem as Will and had to do similar things as him, including copying the dlls to the bin folder.
Now things are working in the internal vs.net server but are causing IIS7 to crash.
Ok, it turns out one of the major problems is that I missed the step to update the compilation assemblies in the web.config:
<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
All i had to do was update the assemblies from
%ProgramFiles%\Microsoft ASP.NET\ASP.NET MVC Beta
Also get the most recent Microsoft.Web.MVC from codeplex
to update my futures assembly too.
add in 2 lines to the web.config
This one to the <assemblies> Section:
<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
This one to the <namespaces> section:
<add namespace="System.Web.Mvc.Html"/>
Then i had to update all the <%using (Html.Form()) to <%using (Html.BeginForm())
On one code file i had to add the System.Web.Mvc.Html; namespace
My stuff is based on Rob Conery's MVC Storefront, so anyone using that should be able to follow the above.
Hope it helps someone out there.
Disregard this... I'm a loser - it's Microsoft ASP.net in program files... not just ASP.net
Maybe this should be a second question, but I think keeping it all in one place might help.
When running the Beta installer nothing ends up changing on my PC. I don't see the folder in the Program Files folder... no assemblies are added to the GAC... even the installer gets to the last step and then hangs for around 10 minutes or so.
I've uninstalled and reinstalled a couple times now without any luck.
Anyone having a similar problem?
The problem with AutoFac has now been resolved in Revision 454 of the AutoFac code base
http://code.google.com/p/autofac/issues/detail?id=86&can=1
Im trying to find out how the new ModelBinder works, as far as I can see it's very different, but i haven't managed to find out how it works yet..
My old looked like:
public class GuestbookEntryBinder : IModelBinder
{
#region IModelBinder Members
public object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState)
{
if (modelType == typeof(GuestbookEntry))
{
return new GuestbookEntry
{
Name = controllerContext.HttpContext.Request.Form["name"] ?? "",
Website = controllerContext.HttpContext.Request.Form["website"] ?? "",
Message = controllerContext.HttpContext.Request.Form["message"] ?? "",
};
}
return null;
}
#endregion
}
The new one looks like:
#region IModelBinder Members
public ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
throw new NotImplementedException();
}
#endregion
Any hints?
I use Autofac as my DI container. A null container exception gets thrown when trying to dispose of the container objects.
Yup, also use Autofac as DI container.
Get same issue as this guy
http://groups.google.com/group/autofac/browse_thread/thread/68aaf55581392d08
No idea if a fix is possible but cant continue until this is fixed ......
After struggling with this for most of the day, I figured I'd post my solution here. Maybe this is normal Visual Studio behavior but I never noticed it before...
On my existing project, I actually had to manually move the Beta files to the Bin folder. For whatever reason, just browsing to it with Add Reference wasn't working...
Html.TextBox - value now is object, not string.
So, hidden errors possible (not at compile time and even not at runtime), for example I've used this overloaded method earlier Html.TextBox(string name, object htmlAttributes). Now my attrs go into textbox value.
About the Autofac issue. There is a thread on the autofac discussion group about the need to update the controller factory to be compatible with the Beta release of the MVC framework
http://groups.google.com/group/autofac/browse_thread/thread/68aaf55581392d08
I hope they post a new version very very soon :-)
When I upgraded from Preview 5 to Beta I had difficulty locating the generic overloads of ActionLink. It appears that those are not included in the main release of ASP.NET MVC but are being shipping as "futures".
I found the necessary assembly (Microsoft.Web.Mvc) # http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=aspnet&ReleaseId=18459
There is a breaking change in the ViewContext constructor. It has changed from:
ViewContext(ControllerContext context, string viewName, ViewDataDictionary viewData, TempDataDictionary tempData)
to:
ViewContext(ControllerContext context, IView view, ViewDataDictionary viewData, TempDataDictionary tempData)
This broke my code because I am using MvcContrib.Services.IEmailTemplateService, which takes a ViewContext in its RenderMessage method. To get an IView from the template name, I am doing the following:
var view = ViewEngines.DefaultEngine.FindView(controllerContext, viewName, null);
Not sure if this is the best practice, but it seems to work.
This is now broken:
<%=Html.TextBox("Name", new Hash(#class => "required"))%>
In Preview 5 the above would bind the value of ViewData.Model.Name to the textbox. This still works:
<%=Html.TextBox("Name")%>
But if you want to specify html attributes, you must also specify the value as follows:
<%=Html.TextBox("Name", ViewData.Model.Name, new Hash(#class => "required"))%>
Actually this is not really safe. If there is any chance ViewData.Model might be null you need to do something like this:
<%=Html.TextBox("Name", ViewData.Model == null ? null : ViewData.Model.Name, new Hash(#class => "required"))%>
This change seems counter to the Beta release notes:
"...in order to reduce overload
ambiguity...the value parameter was changed
from object to string for several
helper methods."
The value parameter for TextBox used to be string, and it was changed to object. So to avoid ambiguities they had to remove the one overload that I use the most. :(
IMHO, every HTML helper method should have overloads that allow binding in all cases without specifying the value. Otherwise we will end up with inconsistent view code that will confuse future devs.
If you are using Html.Form from the futures assembly (Microsoft.Web.Mvc) you might get a name collision on the FormMethod enum. For example:
Html.Form<FooController>(c => c.Bar(), FormMethod.Post, new Hash(#class => "foobar"))
This will complain that FormMethod is an ambiguous reference between Microsoft.Web.Mvc and System.Web.Mvc. This is quite sad because IMHO BeginForm does not provide a viable option due to its lack of an override that uses a lambda expression. Your only option is to use magic strings, which resist refactoring.
The best solution, it seems, is to put the following into every view that uses FormMethod:
<%# Import Namespace="FormMethod=Microsoft.Web.Mvc.FormMethod"%>
Ugh. Hopefully this is temporary. I expect that the futures assembly can be changed to use the enum from System.Web.Mvc. Or much better yet, hopefully they overload BeginForm to use expressions.
It seems that Html.Image is broken. As of preview 5 it was moved to the futures assembly. I cannot imagine why. Anyway, the error is:
Method not found: 'Void System.Web.Mvc.UrlHelper..ctor(System.Web.Mvc.ViewContext)'
The best solution I can see is to replace this:
<%=Html.Image("~/Content/Images/logo.jpg") %>
with this:
<img src="<%=Html.ResolveUrl("~/Content/Images/logo_350.jpg")%>" />
What Will said above, except that in addition to deleting the assemblies from the GAC and re-adding the references I also had to run the Beta installer again (putting the right assemblies in the GAC this time, though I'm just using a file reference).
I suspect if I'd deleted the Preview 5 assemblies from the GAC (and I've no idea how they got in there either) before I ran the installer, everything might have been OK. Worth trying.
In the unlikely event that anyone else out there is as daft as me and working on Vista, you may not need to do the registry hacking above in order to delete the old assemblies - just run gacutil from an admin command prompt. Doh!
I found that updating the web.config namespaces element with the namespaces from a blank project fixed my problems. I also had to update my ModelBinders due to the interface change.

Resources