I have developed an MVC5 application for one of our client. It works fine. Now we have more clients where all the functionalities are same, but the view is different for each client(Not only the layout, but the html structure itself is different in each view).
What I was doing to distinguish the clients is to provide different urls, adding a client identifier (because we need to identify the client even before login), and filtereing it in the RouteConfig as given below:
routes.MapRoute("ClientRoute", "{client}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id =
UrlParameter.Optional },
new RouteValueDictionary
{
{ "client", "icici|federal|pnb|sbi" }
});
where the icici,federal,pnb and sbi are the valid clients.
and I could use this below code to distinguish the clients for any client specific logic.
var clientName = HttpContext.Current.Request.RequestContext.RouteData.Values["client"].ToString();
What I want is to have separate View folders for each client
Views (Default, should be taken from here if not found in other locations)
ICICI_Views
SBI_Views
FEDERAL_Views
PNB_Views
....
these folders will have the layout and cshtml files.
Any action having return View() or return View("viewname") should pick the corresponding views from the respected client folders.
Please help me if anyone know any solution to implement this (like configuring RouteConfig or DisplayModeProvider class, etc). I dont want to have a if-else check in each return view statement and specify the full path.
Thanks in advance :)
You can specify the path of the view while returning from action method, For example if the client is ICICI then return View("~/ICICI_Views/Home/Index.cshtml"); and if no client found you can use return View();
return View("~/ICICI_Views/Home/Index.cshtml");
Related
Working on a MVC Application that basically is a front end database of my City's listings/Directory.
Registered Listings are called like the following:
something.com/Listings/View/{some-guid}
Is it possible to display in this format:
something.com/{slug version of the destination name}
or
something.com/kfc-arabia
This would be of great ease to share the links with clients, also SEO Friendly.
I think it would be difficult to match your slug at the root of your application as this would not allow for other routes in your application. However if you can achieve something like what you want by doing the following
something.com/directory/{slug}
With this you would just need a simply rule to match this to a suitable action that takes your slug as a parameter.
e.g.
context.MapRoute("Listings_by_slug",
"directory/{slug}",
new { controller = "Listings",
action = "ViewBySlug" });
and your action would be something like this
public ActionResult ViewBySlug(string slug){
var listingGuid = _service.GetGuidFromSlug(slug);
return RedirectToAction("View", "Listing", new { id = listingGuid);
}
UPDATE
If you really want that then you could have a route like
context.MapRoute("Listings_by_slug",
"{slug}",
new { controller = "Listings",
action = "ViewBySlug" });
You'd want to put this after other routes you need as this route matches anything to your application. In effect it should be your last declared route. Also you could not have a controlller with the same name as a slug
I've created a controller, called ClientController.cs and VS automatically created the necessary View files in /Views/Client. But I wanted to get these pages in a different URL... So, it is /Client but I need it at /admin/client.
What should I change?
Thank you!
It's not clear what your functionality will be in the long run, but here are a few options that allow you to get the URL format you want:
Perhaps you want a controller called "Admin" and an action called "Client". This would give you a path of /Admin/Client by default
Alternatively, you can change your route maps. For example, the following with route /Admin/Client to the Index of your Client controller:
routes.MapRoute(
"Default", // Route name
"Admin/Client/{action}", // URL with parameters
new { controller = "Client", action = "Index" } // Parameter defaults
);
Or maybe even go a far as using "Areas", depending on what you need. Have a Google of that if you're interested in learning more
If you want it to be admin/client, then using the default routing you should create an Admin Controller with an ActionResult method called Client. Your views folder should have an admin folder with your client view inside.
I haven't done a lot of MVC but i believe this is what you do.
I have a site that has basic functionality, but can be overriden based on different clients and different partners. The Routing is set up to handle the client name and the partner name as part of the route:
routes.MapRoute(
"DefaultRoute", // Route name
"{client}/{portal}/{controller}/{action}/{id}", // URL with parameters
new { client="UNKNOWN", portal="UNKNOWN", controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "Enterprise.Portal.Controllers" }
);
I have a helper class to determine if a view exists that will supercede the normal view. The site has different clients and each client has different partners. These clients can provide HTML if they do not want the default views, and the partners can do the same. I keep these alternate views in a folder. The helper class takes the information and if an alternate view exists, returns the file path to this view. If it returns null or empty string, the normal view is used.
public static string ViewPath(string basePath, string client, string partner, string controller, string viewname)
// This returns something like C:\Sites\Portal\UI\ClientName\PartnerName\ControllerName\View.cshtml
In my controller, if this returns a non null or empty value, How do I provide that view to be used. Here is what I did, which doesn't work:
if (String.IsNullOrEmpty(this.model.CurrentViewLocation))
{
return View(model);
}
else
{
return View(this.model.CurrentViewLocation, model);
}
I am getting the following error, because obviously the return View() constructor can not use path names, only View names. Is there a way to accomplish this? I can convert the paths to Virtual Web Paths if needed like "~\UI\Client\Partner\Controller\View.cshtml".
Server Error in '/' Application
The view 'C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.aspx
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.ascx
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.aspx
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.ascx
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.cshtml
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.vbhtml
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.cshtml
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.vbhtml
I'm guessing a better way to do this would be to add the Client folder and the Partner folder to the Location Formats of the view engine which are used searched for the views. But the format string only includes {0} for the controller and {1} for the viewname. I would need to override it to pass the Client and the partner as well, which are both passed via the route.
I can convert the paths to Virtual Web Paths if needed like
"~\UI\Client\Partner\Controller\View.cshtml".
Yes, that's precisely what you should do because that's what the View method expects - a relative path to the root of the website:
return View("~/UI/Client/Partner/Controller/View.cshtml", someViewModel);
I have an architecture where I have numerous objects I can configure. Examples of URLs I want are as follows:
/configuration/building/add
/configuration/building/edit/1
/configuration/group/add
/configuration/group/edit/1
I have a Configuration controller but how do I intercept or deal with building/add and building/edit/1 etc... If it were AddBuilding I could simply add an AddBuilding() function, and similarily how do I get it to work for configuration/building/edit/
Here's what you can do for the first one - open up the Global.asax.cs file of your site and put this in RegisterRoutes before the standard MVC catch-all route (the one that uses the route "{controller}/{action}/{id}"):
routes.MapRoute("AddBuilding", "configuration/building/add",
new { controller = "Configuration", action = "AddBuilding" });
The others will be the same, but different names (first parameter) and action, whislt the edit routes but would include an {id} route placeholder and route parameter (but not optional - unlike the MVC default route):
routes.MapRoute("EditBuilding", "configuration/building/edit/{id}",
new { controller = "Configuration", action = "EditBuilding" });
By leaving the id off the route defaults we make it required. I'm assuming this, because I'm guessing the Url /Building/Edit doesn't logically map to anything.
As a side node - including verbs in your urls isn't really in keeping with REST methodology, however you're not the first to do it by a long way (I include myself in that too). That said - trying to keep to it usually makes your life a lot easier, as you'll find your controllers will be cleaner, as will your route table, and your site's URL space will be a lot smaller and more obviously hierarchical. This last point is - handy for zooming around the site at dev time, but more importantly it's crucial for SEO.
So (I've commented this code heavily, hopefully enough to provide some nuggets of knowledge!):
public class ConfigurationController{
////HTTP GET /Buildings
/// DISPLAYS BUILDINGS
public ActionResult Buildings(){
//get model and return view that shows all buildings with perhaps a
//partial in that for creating a new one (or you can use another action)
//The HTML form on that view will POST to the URL handled by the method below.
}
////HTTP POST /Buildings
/// CREATES A NEW BUILDING
//use ActionName here to make this and the method above accessible through
//the same URL
[ActionName("Buildings")]
[HttpPost]
public ActionResult CreateBuilding(BuildingModel model){
//validate the model, create the object and return the same
//view as the Buildings() method above (after re-loading all the
//buildings. Or, you can issue a redirect, effectively transferring
//control back to the method above.
}
////HTTP GET /Configuration/Building/id
///DISPLAYS A BUILDING
public ActionResult Building(int id){
//get building and return view, which also contains Edit functionality
}
////HTTP POST /Configuration/Building/id
///EDITS A BUILDING
[HttpPost]
public ActionResult Building(int id, BuildingModel model){
//very similar to the CreateBuilding method - and again you might
//either simply return a building view at the end, or redirect
//to the method above.
//Note that we don't need [ActionName] here because this and the
//get method can have the same method names, because they are overloads
//i.e. since they have different method signatures we can call them the same
//thing in code.
}
}
I've left off the group stuff to keep it short, and hopefully you'll be able to see how to do it from there.
With this in place, we only need at most two routes in Global.asax.cs - although I think the order will be important:
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("IndividualBuilding", "/configuration/buildings/{id}",
new { controller = "Configuration", action = "Building" });
routes.MapRoute("Buildings", "/configuration/buildings",
new { controller = "Configuration", action = "Buildings" });
Now we are using the HTTP verbs to signify what we intend to do with a particular request, and our URLs have become more 'logical'.
Another refactor
If you want to be 'clever' you can lump both buildings and groups under two routes
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("Individual", "/configuration/{controller}/{id}",
new { controller = "Configuration", action = "List" });
//again, handles GET and POST
routes.MapRoute("Collection", "/configuration/{controller}",
new { controller = "Configuration", action = "Single" });
Now you do both buildings and groups controllers as I showed above, but replace Buildings (remembering the ActionName attribute on the second method) with List and Building with Single.
One final thing to consider is that because of the default MVC route:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Default", action="Home", id = UrlParameter.Optional });
Both of your two controllers can still be routed via /Buildings/Single/1 or /Groups for example. This is a minor issue (dupe content isn't great SEO) but it can be something that people can use to sniff your site.
If you absolutely want to prevent this other url format; you can take out the default route, meaning you'd have to explicitly route other stuff that might already work (not a great issue).
Or you can use a little trick that will make it far harder: use explicit [ActionName] attributes with characters in the route name that won't be allowed through IIS - e.g. ":Single" or ":List", and then adjust our two routes from a couple of code blocks back accordingly.
So firstly you can create a controller action called AddBuilding() as you have hinted.
Then in your Global.asax file in the RegisterRoutes method you can add a route like so:
routes.MapRoute(
"AddBuilding", // Route name
"configuration/building/add", // URL with parameters
new { controller = "Configuration", action = "AddBuilding" }
);
You should not though that you will likely still be able to access the page using "/configuration/addbuilding" because of your default route mapping.
You edit one will be similar expect you will want to map the ID value for this:
routes.MapRoute(
"EditBuilding", // Route name
"configuration/building/edit/{id}", // URL with parameters
new { controller = "Configuration", action = "AddBuilding", id = UrlParameter.Optional }
);
I think you will need to add this code with the default MapRoute setup to ensure that one does not take priority
Another approach would be to create a Configuration MVC area, and then have a building and group controller in that Area.
You can do that by Attribute Routing in ASP.NET MVC 5. Something like following;
// eg: /reviews
[Route(“reviews”)]
public ActionResult Index() { … }
// eg: /reviews/5
[Route(“reviews/{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg: /reviews/5/edit
[Route(“reviews/{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
You can add multiple route for the same controller as well. For details please check here
I have the basic Master / Detail Views working great with the default ASP.NET MVC Route; however I would like to build some URLs like this:
/Class/Details/5 -- General Detail view [Working]
What I'm not sure about (and I'm not tied to this URL format, just something roughly equalivent.)
/Class/5/Details/Logs -- Detail View with Logs
/Class/5/Details/Status -- Detail View with current Status
Another way to put this, is like this:
/{controller}/{id}/{controllerSpecificMaster}/{action}/
What I'm trying to avoid, is cluttering up my Views\Class directory with a bunch of Views, which are all basically derivatives of the Details view.
I'm on ASP.NET MVC 1 and .NET 3.5 SP1.
The first thing you need to get down are your routes. You may have already done this, but in case you haven't, here's a route entry that will handle your custom route needs:
routes.MapRoute("Master_Detail",
"{controller}/{id}/{controllerSpecificMaster}/{action}",
new { controller = "Class",
action = "Index",
id = UrlParameter.Optional,
controllerSpecificMaster = "Details"
});
Then, in your action methods where you want to use the route-specified master page, just include the route key in your method arguments, and then pass it to the view:
public ActionResult Logs(int id, string controllerSpecificMaster)
{
//do something
//return view with master name as argument
return View("Logs", controllerSpecificMaster);
}
If you have to do this a lot, I would suggest creating a custom view engine and override the FindView() method.