easiest way to change path of Views for a certain controller - c#

I have a Controller called BaseController and Controller1 and Controller2 inherit from it.
All of the views for these controllers are under ~/Views/Base/ the reason for this is that Controller1 and 2 do the exact same thing but have custom attributes for certain things on some overriden actions.
I want to be able to point to ~/Views/Base as the location to look for views in both Controller1 and Controller2. Now can i do this without having to implement my own ViewLocator as per Dale's solution in this post Views in separate assemblies in ASP.NET MVC
I would prefer to not throw all these views into ~/Views/Shared as they aren't really shared except in between these two Controllers.

You could write a custom view engine in order to add this new location in the search list:
public class MyViewEngine : RazorViewEngine // WebFormViewEngine - if you are using WebForms
{
public MyViewEngine()
{
ViewLocationFormats = base.ViewLocationFormats.Union(new[]
{
"~/Views/Base/{0}.cshtml",
"~/Views/Base/{0}.vbhtml",
"~/Views/Base/{0}.aspx",
"~/Views/Base/{0}.ascx",
}).ToArray();
}
}
which will be registered in Application_Start:
ViewEngines.Engines.Add(new MyViewEngine());

Related

How do I stop MVC5 RenderAction looking for .aspx files

I added MVC to my existing webforms project. All is going well except for RenderAction is looking for .aspx files
The view '_Mainmenu.cshtml' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Areas/NewPages/Views/Shared/_Mainmenu.cshtml.ascx
The view is
~/Areas/NewPages/Views/Shared/_Mainmenu.cshtml
And it does exist in that folder. Can anybody help me sort this out.
Everything else MVC is working OK I even have PITA EntityFramework working too
Any help would be appreciated
The view '[viewname]' or its master was not found or no view engine supports the searched locations indicates that you're using default view engine which prioritizes web forms view engine (the path shown as ~/Areas/NewPages/Views/Shared/_Mainmenu.cshtml.ascx means that MVC view engine prioritizes searching ASPX & ASCX files instead of Razor cshtml files). To change this behavior which MVC uses Razor view engine by default, insert these lines into Application_Start method on Global.asax:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
// ViewEngines.Engines.Add(new WebFormViewEngine()); => optional webforms engine registration
Additionally, if default Razor view engine still can't recognize cshtml files in areas properly, you need to create a custom view engine class which inherits RazorViewEngine and setting AreaViewLocationFormats in its constructor like this:
public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine()
{
// Route parsing convention for view engines:
// {0} means action method name
// {1} means controller class name
// {2} means area name
AreaMasterLocationFormats = new[]
{
"~/Areas/{2}/Views/Shared/{0}.cshtml"
};
AreaViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
// other view search locations here
};
AreaPartialViewLocationFormats = AreaViewLocationFormats;
}
}
Note that the custom view engine will search all view pages inside areas specified by controller action method depending on routes defined in AreaViewLocationFormats.
Then, register custom view engine class at the same place as RazorViewEngine, i.e. in Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
// clear all view engines repository first
ViewEngines.Engines.Clear();
// register Razor view engine only
ViewEngines.Engines.Add(new RazorViewEngine());
// register custom view engine class here
ViewEngines.Engines.Add(new CustomViewEngine());
// other initialization codes here
}
Similar issues:
ASP.NET MVC: When should I create custom View Engine
How do I implement a custom RazorViewEngine to find views in non-standard locations?
In the "Global.asax" file, find "Application_Start()"
Then, Please input this code --
RemoveWebFormEngines();

Work out if Controller is in an Area

Is there a way to check whether the Controller which invoked a method comes from a Controller which is within an Area?
For example, I have a class which inherits from AuthorizeAttribute e.g.
public class CustomAuthorize: System.Web.Mvc.AuthorizeAttribute
{
public CustomAuthorize()
{
...
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// TODO - Check if the controller is from an Area
}
}
I then have some controller actions which are decorated with relevant Roles (as well as a few other custom attributes) e.g.
[CustomAuthorize(Roles ="Administrator")]
[HttpGet]
public virtual ActionResult Index()
{
...
}
In the TODO section above, I'd like to see if the Controller is one of the controllers from one of my Areas. I know that my controllers which are in an area will be in the ProjectName.Areas.xxx.Controllers namespace (where xxx is the Area name), whereas ones which aren't will be in the ProjectName.Controllers namespace.
Is there some way (using reflection perhaps?) that from within the AuthorizeCore function above that I can work out the specific area (or namespace that it came from) so that I can implement some custom functionality?
You can get it from the RouteData.DataTokens:
httpContext.Request.RequestContext.RouteData.DataTokens["area"]
That will return null if your controller is not in an area or the name of the area if your controller is in an area.
I'm not too familiar with MCV's Area concept, but I've found this link from a Google search. Perhaps it may help you out.
ASP.NET MVC - Get Current Area Name in View or Controller

Overriding / Extending an MVC Controller / Area

I'm currently working on an MVC project and I'm trying to figure out how I might go about extending the routes of an existing Controller within an Area, specifically from another project.
For instance, I have a Controller with an area that looks like the following :
namespace MyProject.Areas.Foo.Controllers
{
[Authorize]
public class FooController : ApplicationController
{
//code
}
}
And what I would like to do, is be able to define another Controller, within a separate project that could extend this like so :
namespace MyOtherProject.Areas.Foo.Custom.Controllers
{
public class FooController : ApplicationController
{
public string Bar()
{
return "Bar";
}
}
}
Basically, I would like the controllers to almost function as if I was using the partial keyword (so that I could call any of the actions in the original or the new one).
The Main Problem
What I am really trying to accomplish is that I have a main project with several areas and another area of my solution with various client folders. I want to be able to essentially extend the base controllers for my main project and add client-specific actions within these client folders so that they can be used in the main project. I'm already doing this with certain MVC Views, but I was hoping I could accomplish it with controllers as well.
What I've tried
I tried using the partial keyword on both declarations of the class, but since they are in different projects / assemblies, I don't think that works.
I defined a build event that would move the custom DLL into the bin directory of the main MVC project, but that didn't seem to work as expected.
I've tried various approaches for inheritance, hoping the new class would get picked up, but those didn't work (received the duplicate controller declaration error).
I've read about trying to use a custom ControllerFactory but I wasn't sure how to implement it.
I've tried defining custom namespace routing parameters in the AreaRegistration section to pick up the new controller like the following example.
Routing Example (AreaRegistration)
context.MapRoute(
AreaName,
String.Format("{0}/{{action}}/{{id}}", AreaName),
new { controller = AreaName, action = "Index", id = UrlParameter.Optional },
new[] {
String.Format("MyProject.Areas.{0}.Controllers", AreaName),
String.Format("MyOtherProject.Areas.{0}.Custom.Controllers", AreaName)
}
);
Update
I attempted an approach seen here as per some of the comments discussion that involved simply handling this via inheritance :
// Main Project
namespace MyProject.Areas.Foo.Controllers
{
[Authorize]
public class FooController : ApplicationController
{
public ActionResult Index()
{
return View();
}
}
}
// This is in another project / namespace / assembly
namespace MyOtherProject.Foo.Controllers
{
public class CustomFooController : MyProject.Areas.Foo.Controllers.FooController
{
[Route("Foo/Bar")]
public string Bar()
{
return "Bar";
}
}
}
So my current steps are as follows :
Inherited from the base FooController in the main project within another project / solution.
Set up attribute routing to access the custom controller to avoid conflicting routes from the main project.
Created a Build Event that moves the custom DLL into the main project when built (so it will be accessible) from the new custom project.
This didn't seem to make any difference. I tried going to the Foo/Bar url but it just threw a 404 as if it didn't see it at all. The CustomFooController.cs file is in it's own separate project and is just a class file and not an MVC project. Is this correct? Do I need to set the routing rules in the main project?
Controller Inheritance
Using inheritance as Chris mentioned in the comments section will likely be the best way of going about this as well. This is especially true if you are already deriving from another base controller class like ApplicationController in your example :
// ProjectA is assumed to be your "main" MVC application
public class CustomFooController : ProjectA.Controllers.FooController
{
[Route("Foo/Bar")]
public ActionResult Bar()
{
return Content("Bar");
}
}
The attribute routing here is extremely important as you don't want your existing routes to confuse your two controllers or overlook them.
Registering Attribute Routes
Since you are using attribute routing via the [Route] attribute within your ProjectB section, you'll want to ensure that you explicitly set it within the RouteConfig.cs of your ProjectA project so that it can properly identify it through the Routes.MapMvcAttributeRoutes() method as seen below :
public static void RegisterRoutes(RouteCollection routes)
{
// This is important to set up your Route Attributes
routes.MapMvcAttributeRoutes();
// Route declarations omitted for brevity
}
Likewise, if you are using Areas, you'll want to configure this within the respective AreaRegistration.cs file as well :
public override void RegisterArea(AreaRegistrationContext context)
{
// Wire up any attribute based routing
context.Routes.MapMvcAttributeRoutes();
// Area routing omitted for brevity
}
Scoping Routes
Finally, the last thing you'll want to make sure to do is properly "scope" your routes to prioritize your main namespace within the RouteConfig.cs of your main ProjectA application :
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Foo", action = "Index", id = UrlParameter.Optional },
// This will prioritize your existing Controllers so they work as expected
namespaces: new[] { "ProjectA.Controllers"}
);
}
Getting References Across
You mentioned using a Build Event to copy over the DLL from your ProjectB project into your main ProjectA project, which should be fine in this case. You will basically need some way to access it and a simply xcopy like the following should be fine in most scenarios :
xcopy /E /Y /S "$(ProjectName).dll" "$(SolutionDir)\ProjectA\Bin\"
Putting It All Together
If you have wired up all of these steps correctly, you should be able to Clean / Rebuild your existing solution. After doing so, double-check to ensure that you have the appropriate DLL within your ProjectA bin directory :
If that is there, then you are on the right track and should be able to run your main application and navigate to ~/Foo to see the following :
Likewise, navigating to ~/Foo/Bar should pick up the appropriate attribute route that was defined in your other Controller and serve the proper content :

How to put .Mobile views in another folder with MVC4?

In MVC 4, you can just append .Mobile to any view and mobile devices will automatically get served that view from the same controller. Is there a way to store the .Mobile files in a different folder? I really want to store the desktop files in one "Area" and the mobile in another "Area". Anyone know of something like this?
This can easily be accomplished by creating a custom implementation of RazorViewEngine and adding the custom mappings to the ViewLocationFormats. It is important to remember to add the custom mappings to the beginning of the ViewLocationFormats array as they are more specific than the existing mappings.
namespace MobileViewsInMobileFolder.Utility {
public class MyCustomViewEngine : RazorViewEngine {
public MyCustomViewEngine() {
List<string> existingViewLocationFormats = ViewLocationFormats.ToList();
//Folder Structure: Views\Home\Desktop and Views\Home\Mobile
existingViewLocationFormats.Insert(0, "~/Views/{1}/Desktop/{0}.cshtml");
existingViewLocationFormats.Insert(0, "~/Views/{1}/Mobile/{0}.cshtml");
//Folder Structure: Views\Desktop\Home and Views\Mobile\Home
existingViewLocationFormats.Insert(0, "~/Views/Desktop/{1}/{0}.cshtml");
existingViewLocationFormats.Insert(0, "~/Views/Mobile/{1}/{0}.cshtml");
ViewLocationFormats = existingViewLocationFormats.ToArray();
}
}
}
And then make sure to add the custom view engine in the Application_Start
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MyCustomViewEngine());

how does asp.net mvc relate a view to a controller action?

I have opened a sample ASP.NET MVC project.
In HomeController I have created a method (action) named MethodA
public ActionResult MethodA()
{
return View();
}
I have right clicked on MethodA and created a new view called MethodA1
Re-did it and created a new view called MethodA2.
How is this magical relationship done? I looked for the config to tell the compiler that views MethodAX are related to action MethodA, but found none.
What view will the controller return when MethodA is called?
The convention is that if you don't specify a view name, the corresponding view will be the name of the action. So:
public ActionResult MethodA()
{
return View();
}
will render ~/Views/ControllerName/MethodA.cshtml.
But you could also specify a view name:
public ActionResult MethodA()
{
return View("FooBar");
}
and now the ~/Views/ControllerName/FooBar.cshtml view will be rendered.
Or you could even specify a fully qualified view name which is not inside the views folder of the current controller:
public ActionResult MethodA()
{
return View("~/Views/Foo/Baz.cshtml");
}
Now obviously all this assumes Razor as view engine. If you are using WebForms, replace .cshtml with .aspx or .ascx (if you are working with partials).
For example if there is no view it will even tell you where and in what order is looking for views:
Remember: ASP.NET MVC is all about convention over configuration.
The MVC framework use convention over configuration. The framework calls the ExecuteResult on the ViewResult object (created by the return View();). The framework by convention then looks in a number of locations to find a view
If you are using areas the framework will look in the following locations for a view.
/Areas//Views/ControllerName/ViewName.aspx
/Areas//Views/ControllerName/ViewName.ascx
/Areas//Views/Shared/ViewName.aspx
/Areas//Views/Shared/ViewName.ascx
/Areas//Views/ControllerName/ViewName.cshtml
/Areas//Views/ControllerName/ViewName.vbhtml
/Areas//Views/Shared/ViewName.cshtml
/Areas//Views/Shared/ViewName.vbhtml
Without areas (or if you are using areas and no view has been found) the framework will look at the following locations
/Views/ControllerName/ViewName.aspx
/Views/ControllerName/ViewName.ascx
/Views/Shared/ViewName.aspx
/Views/Shared/ViewName.ascx
/Views/ControllerName/ViewName.cshtml
/Views/ControllerName/ViewName.vbhtml
/Views/Shared/ViewName.cshtml
/Views/Shared/ViewName.vbhtml
As soon as the Framework tests a location and finds a file, then the search stops,
and the view that has been found is used to render the response to the client.
There are a number of overriden versions of the View method. The most common one is to render a specific view, outside of the framework convention, by calling it by name. For example
return View("~/Views/AnotherIndex.cshtml");
As an interesting footnote, the framework looks for legacy ASP, C# and VB Razor views (aspx, ascx, cshtml and vbhtml) even though you have a specific view engine.
In MVC controller action is not bound to view.
It uses delegate mechanism to pickup the view.
Model Binding(Mapping)
I was looking for the same and I just made a couple of tests and figured out.
It doesn't save anywhere.
To understand how it works; just do these steps:
In your controller, right click, Add View
Then enter a different View Name
and Ctrl F5
you will get Server error in application.
For example if you right click , Add View in following Index action method and type "Index2" in View name, you will get the error.
public class TestController : Controller
{
// GET: Test
public ActionResult Index()
{
return View();
}
}
So basically there is a 1-1 matching between action name and View name. And you cannot add view for the same method so there is no need to save in a config file.
Now change the view file name in Visual Studio from Index2.cshtml to Index.cshtml then Ctrl+F5. You should see it is working.

Categories