Creating folder in view where I can put my other views - asp.net-mvc

I just got a question about the controller management.
I just try to get folder in my view/{copntroller}/newFolder/currentView.
I was trying to use maprouting, but that doesn't work well(doesn't work at all :D).
I just try to put this code in "RouteConfig.cs"
routes.MapRoute(
name: "Admin",
url: "{controller}/{Folder}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here is a picture of current

Routing doesn't change Views location search. It change Url routing (Read like: Understand what Controller method call when user sends request, but not what View use to render).
If you want change your default View search locations you should redefine ViewEngine like this:
public class CustomViewEngine : RazorViewEngine //Here you inherit from current ViewEngine
{
public CustomViewEngine()
{
ViewLocationFormats = new[]
{
//That's your Views loactions
"~/Views/{1}/Create/{0}.cshtml",
"~/Views/{1}/Edit/{0}.cshtml",
};
}
}
Here {0} - is Your Contoller method name (Action), {1} - Controller name.
You can define many different locations and ViewEngine will search in defined order.
The last thing you should do is register your CustomViewEngine in global.asax.cs method Application_Start() like this:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());

Related

Can I control (manipulate) the address bar using MVC .NET

I would like to invoke this URL:
www.example.com/home/brand1
but have the resulting URL be:
www.example.com/brand1
I want to do this with multiple brands. I know I can have an action in the home controller for each brand which redirects to a brand controller but I don't want a controller for each brand. I imagine I can do this with routing but just don't know how.
routes.MapRoute(
"Default",
"{brandName}",
new { controller = "Home", action = "YourBrandAction", brandName = "" }
); // inside RegisterRoutes method
//Your Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult YourBrandAction(String brandName)
{
//your controller logic...
return View(yourBrandModel);
}
}
You must be very careful with the above route configuration. Some URLs, e.g. www.example.com/login will not direct you to the login page but treat 'login' as a brand name.

ASP.NET MVC 4 Routing I just can't figure out

I'm working on a project in ASP.NET MVC 4 and I'm at a bit of a loss with a particular routing. I have a lot of custom routes already in the project.
I am currently making a bunch of controllers for the frontend of the site (publicly visible part) to be able to do thing like abc.com/OurSeoFeatures that gets routed to /OurSeoFeatures/Index
Is there any way to do this so that the above would route to something like /frontend/OurSeoFeature and another page would route to /frontend/anotherpage and also still have my other routes correctly? It seems to me that the above would hit the default route and if I put something like the following it would just catch all the request and would not let me hit anything else.
routes.MapRoute(
name: "ImpossibleRoute",
url: "{action}/{id}",
defaults: new { controller = "frontend", id = UrlParameter.Optional }
);
Am I just stuck with making a bunch of controllers? I really don't want to make one controller like page and put a bunch of actions there as I don't think its very pretty. Any Ideas?
In order to do what you're asking, you simply need to add a route constraint:
routes.MapRoute(
name: "Frontend",
url: "frontend/{controller}/{action}/{id}",
defaults: new { controller = "OurSeoFeature", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = "OurSeoFeature|Products" }
);
This constraint means the route will only match controllers with the names OurSeoFeatureController or ProductsController. Any other controller will trigger the default route. However, this wouldn't handle redirecting those controllers to /frontend/..., if that's what you're after. Instead, that gets a little more involved.
Firstly, you'll need to create a class that implements IRouteConstraint, in order to supply the controller names you want to redirect to /frontend/.... The reason we need this now, is because we'll need to access those names in an ActionFilter, and we can't do that if we supply a regex constraint like constraints: new { controller = "OurSeoFeature|Products" above. So, the constraint could look something like this:
public class FrontendControllerConstraint : IRouteConstraint
{
public FrontendControllerConstraint()
{
this.ControllerNames = new List<string> { "OurSeoFeature", "Products" };
}
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
string value = values[parameterName].ToString();
return ControllerNames.Contains(value, StringComparer.OrdinalIgnoreCase);
}
public List<string> ControllerNames { get; private set; }
}
Next up, the action filter could look like this:
public class RedirectToFrontendActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.RouteData.Values["controller"].ToString();
var path = filterContext.HttpContext.Request.Url.AbsolutePath;
var controllersToMatch = new FrontendControllerConstraint().ControllerNames;
if (controllersToMatch.Contains(controller, StringComparer.OrdinalIgnoreCase)
&& path.IndexOf(pathPrefix, StringComparison.OrdinalIgnoreCase) == -1)
{
filterContext.Result =
new RedirectToRouteResult(routeName, filterContext.RouteData.Values);
}
base.OnActionExecuting(filterContext);
}
private string routeName = "Frontend";
private string pathPrefix = "Frontend";
}
Now that we have those in place, all that's left is to wire it all up. Firstly, the constraint is applied in a slightly different way:
routes.MapRoute(
name: "Frontend",
url: "frontend/{controller}/{action}/{id}",
defaults: new { controller = "OurSeoFeature", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = new FrontendControllerConstraint() }
);
Finally, you need to add the filter to FilterConfig.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new RedirectToFrontendActionFilter());
}
One warning here is that because I'm checking against Request.Url.AbsolutePath, you cannot pass anything in the path that contains the word frontend. So make sure all controllers, actions and route values added to the path, do not contain that. The reason is that I'm checking for the existence of /frontend/ in the path, to ensure that the matched controllers will only redirect to that route if they they're not already using it.
There are a lot of added things you could do with that setup, but I don't know your requirements. As such, you should treat this code simply as a skeleton to get started, making sure to test that it does what you want it to do.
Updated per comments
I'll leave everything above there, just in case someone finds that useful. To address what you'd like to do, however, we need a different approach. Again, we need some route constraints, but the way I see this working is to flip your idea on its head and make the frontend the default route. Like so:
routes.MapRoute(
name: "Backend",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = "Home|Backend" }
);
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Frontend", action = "Index", id = UrlParameter.Optional },
constraints: new { action = "Index|OurSeoFeature" }
);
Just as before, I've applied some constraints to get the correct behaviour. In particular, for this constraint:
constraints: new { controller = "Home|Backend" }
if you have a lot of controllers that aren't part of the frontend, it might be an idea to implement IRouteConstraint to keep a list of the controller names there. You could even go as far as deriving all of your backend controllers from a base controller, so you can grab all of them with reflection in the IRouteConstraint implementation. Something like this:
public BackendController : Controller
{
//
}
Then:
public AdminController : BackendController
{
//
}
Constraint:
public class BackendConstraint : IRouteConstraint
{
// Get controller names based on types that
// BackendController
}
This same idea also applies to getting the action names of FrontendController for the second constraint. The only thing you need to be careful of here is that you don't have any backend controllers which have the same name as an action on your FrontendController, because it will match the wrong route.
I appreciate the question is over a year old with an accepted answer but the accepted answer involves route constraints when none are necessary. It's really just as simple as:
routes.MapRoute("SEO", "OurSeoFeatures",
new { controller = "frontEnd", action = "OurSeoFeatures"});
The basic idea of the route is controller/action.
So if you want to hit the OurSeoFeatures controller's index action then you have to give your route like
routes.MapRoute(
name: "BasicController",
url: "{controller}/{action}/{id}",
defaults: new { controller = "OurSeoFeatures",action="Index", id = UrlParameter.Optional }
);
In your case you have left out the controller from your route url. Please specifiy the controller also as part of URL and have a default controller.

Html.Action() results in "No route in the route table matches the supplied values"

This issue has been discussed many times, but I haven't found a resolution for my particular case.
In one of my Umbraco (6) views I am calling a controller method by using
#Html.Action("Index", "CountryListing");
This results in the "no route in the route table" exception.
I have been fiddling around with the RegisterRoutes method to no avail. I wonder if it is even used as the site still functions when I empty the RegisterRoutes method. This is what it looks like now:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I have also tried adding an empty "area" to the call like this
#Html.Action("Index", "CountryListing", new {area: String.Empty});
I am using #Html.Action statements in other places and they DO work, so I do understand why some of them work and others don't, but the main problem now is getting my country listing action to work.
You can solve this by doing the following
Make sure your Controller is extending Umbraco's SurfaceController
Name it YourName*SurfaceController*
Add the [PluginController("CLC")] 'annotation' (I am from Java) to your controller. CLC stands for CountryListController. You can make up your own name of course.
Add the PluginController name (CLC) to the Html.Action call as a "Area" parameter.
My controller:
[PluginController("CLC")]
public class CountryListingSurfaceController : SurfaceController
{
public ActionResult Index()
{
var listing = new CountryListingModel();
// Do stuff here to fill the CountryListingModel
return PartialView("CountryListing", listing);
}
}
My partial view (CountryListing.cshtml):
#inherits UmbracoViewPage<PatentVista.Models.CountryListingModel>
#foreach (var country in Model.Countries)
{
<span>More razor code here</span>
}
The Action call:
#Html.Action("Index", "CountryListingSurface", new {Area= "CLC"})
you can use null instead of using String.empty
#Html.Action("Index", "CountryListing",null);
if you are using area, you have to override RegisterRoutes for every area
public override string AreaName
{
get
{
return "CountryListing";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"CountryListing_default",
"CountryListing/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
and i recommend you take a look at the same problem:
https://stackoverflow.com/a/11970111/2543986

Routing is finding the controller in my areas, but not the views

I'm trying to use Maarten Balliauw's Domain Route class to map sub-domains to the areas in an MVC2 app so that I have URLs like:
http://admin.mydomain.com/home/index
instead of:
http://mydomain.com/admin/home/index
So far, I've only had partial success. Execution is being routed to the correct controller in the correct area, but it cannot then find the correct view. I'm receiving the following error:
The view 'Index' or its master was not found. The following locations were searched:
~/Views/AdminHome/Index.aspx
~/Views/AdminHome/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
This indicates to me that MVC is looking for the view only in the root views folder and not the views folder within the Area. If I copy the view from the Area's views folder to the root views folder, the page renders fine. This however, completely defeats the purpose of dividing the APP into Areas.
I'm defining the route for the area as:
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "Admin"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.Add(
"Admin_Default"
, new DomainRoute(
"admin.localhost"
, "{controller}/{action}/{id}"
, new { controller = "AdminHome", action = "Index", id = UrlParameter.Optional }
));
}
}
I'm confused as to why it is finding the controller within the Area fine, but not the view.
OK, I figured it out. After downloading the MVC 2 source code and adding it to my solution as outlined here, I stepped through the MVC code. I found that Routes within areas implement the IRouteWithArea interface. This interface adds an 'Area' property to the RouteData which, not surprisingly, contains the area's name. I modified the DomainRoute class so to implement this interface and added a couple of overloaded constructors that took this additional parameter, and it now works exactly as I wanted it to.
The code for registering my route now looks like this:
context.Routes.Add(
"Admin_Default"
, new DomainRoute(
"admin.mydomain"
,"Admin"
, "{controller}/{action}/{id}"
, new { controller = "AdminHome", action = "Index", id = UrlParameter.Optional }
));
If you have share controller names between your areas and your default routes, and it looks like you do, you may need to identify namespaces when you call MapRoute.
For example, if the top-level namespace of your web application is Web, the RegisterRoutes method in Global.asax.cs file would look something like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
null,
new string[] { "Web.Controllers" }
);
and then the RegisterArea moethod of AdminAreaRegistration.cs would look something like this:
context.MapRoute(
"Admin_Default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
null,
new string[] { "Web.Areas.Admin.Controllers" }
);

MVC Areas - View not found

I have a project that is using MVC areas. The area has the entire project in it while the main "Views/Controllers/Models" folders outside the Areas are empty barring a dispatch controller I have setup that routes default incoming requests to the Home Controller in my area.
This controller has one method as follows:-
public ActionResult Index(string id)
{
return RedirectToAction("Index", "Home", new {area = "xyz"});
}
I also have a default route setup to use this controller as follows:-
routes.MapRoute(
"Default", // Default route
"{controller}/{action}/{id}",
new { controller = "Dispatch", action = "Index", id = UrlParameter.Optional }
);
Any default requests to my site are appropriately routed to the relevant area. The Area's "RegisterArea" method has a single route:-
context.MapRoute(
"xyz_default",
"xyz/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
My area has multiple controllers with a lot of views. Any call to a specific view in these controller methods like "return View("blah");
renders the correct view. However whenever I try and return a view along with a model object passed in as a parameter I get the
following error:-
Server Error in '/DeveloperPortal' Application.
The view 'blah' or its master was not found. The following locations were searched:
~/Views/Profile/blah.aspx
~/Views/Profile/blah.ascx
~/Views/Shared/blah.aspx
~/Views/Shared/blah.ascx
It looks like whenever a model object is passed in as a param. to the "View()" method [e.g. return View("blah",obj) ] it searches for the view
in the root of the project instead of in the area specific view folder.
What am I missing here ?
Thanks in advance.
Solved ! A couple of my "RedirectToAction" calls were not specifying the area name explicitly in the routeobject collection parameter of that method. Weird though, that that is required even though the controllers Redirecting are all in the same area. Also, the HtmlActionLinks work fine when I don't specify the new {area="blah"} in its routeobject collection, so I wonder why the controller action calls to RedirectToAction() need that even though both the calling and the called controller actions are all within the same area.
If you use instead of
context.MapRoute(
"xyz_default",
"xyz/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
use
context.MapRoute(
"xyz_default",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
in your
xyzAreaRegistration.cs
then you don't need to explicitly specify your area in any link...
Add the RouteArea attribute on the Controller class so MVC knows to use the "XYZ" Area for the views (and then you can set the AreaPrefix to empty string so routes do not need to start with "XYZ").
[RouteArea("Xyz", AreaPrefix = "")]
public class XyzController : Controller
{
...
}
If this is a routing problem, you can fix it by registering your area routes first. This causes the routing engine to try matching one of the area routes, before matching a root route:
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
If I force an error by renaming one of my views folders in my areas application, I get a different error than yours:
The view 'Index' or its master was not found. The following locations
were searched:
~/Areas/xyz/Views/Document/Index.aspx
~/Areas/xyz/Views/Document/Index.ascx
~/Areas/xyz/Views/Shared/Index.aspx
~/Areas/xyz/Views/Shared/Index.ascx
...and then the usual root view folders..
..which is the pattern of subdirectories it would search if it thought it was in an area.
Check the generated code at MyAreaAreaRegistration.cs and make sure that the controller parameter is set to your default controller, otherwise the controller will be called bot for some reason ASP.NET MVC won't search for the views at the area folder
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SomeArea_default",
"SomeArea/{controller}/{action}/{id}",
new { controller = "SomeController", action = "Index", id = UrlParameter.Optional }
);
}
For those who are looking for .net core solution please use
app.UseMvc(routes =>
{
routes.MapRoute(
name : "areas",
template : "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
});`
If you have some code in main project and some code in areas use the following code.
app.UseMvc(routes => {
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Note make sure you have areas defined in your controller
[Area("Test")]
public class TestController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
I just had the same problem and solved it by setting the ascx's 'Build Action' property to 'Embedded Resource'.
Try this code. Do changes in Area Registration File...
context.MapRoute(
"YourRouteName", // Route name //
"MyAreaName/MyController/{action}", // URL with parameters //
new {
controller = "MyControllerName",
action = "MyActionName", meetId = UrlParameter.Optional
}, // Parameter defaults
new[] { "Your Namespace name" }
);

Resources