Custom Folder Structure in ASP.NET MVC 5 - asp.net

I'm trying to determine if it is possible (or practical) to implement Uncle Bob's Screaming Architecture in ASP.NET MVC 5 rather than using the default folder structure.
Here's a link to a description of the Screaming Architecture: http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html
A hypothetical folder structure would look something like this:
Root
Customers
Controllers
CustomerController.cs
Models
Customer.cs
Views
Index.cshtml
Details.cshtml
Update.cshtml
Employees
Controllers
EmployeesController.cs
Models
Employee.cs
Views
Index.cshtml
Details.cshtml
Update.cshtml
Shared
Views
_Layout.cshtml
Error.cshtml
_ViewStart.cshtml
Web.config
The corresponding URL routes would look like this:
http://www.example.com/customers/ => Customer Index
http://www.example.com/customers/details/1 => Customer Details
http://www.example.com/customers/update/1 => Customer Update
http://www.example.com/employees/ => Employee Index
http://www.example.com/employees/details/1 => Employee Details
http://www.example.com/employees/update/1 => Employee Update
I've created a custom RazorViewEngine and added the appropriate view location formats (e.g. "~/{1}/Views/{0}.cshtml") and partial view location formats (e.g. "~/Shared/Views/{0}.cshtml"). I've also moved the shared _ViewStart.cshtml to the root and merged the Views/Shared folder's web.config with the root-level web.config to avoid having to duplicate these two files in all of the View folders.
Everything works great, however, if I try navigating to an index page (e.g. http://www.example.com/employees/) I get a 403.14 Error (Forbidden). All other routes (including http://www.example.com/employees/index) work just fine.
My guess is that IIS is explicitly blocking the route to the controller's index method because the URL coincides with a folder in the filesystem and directory browsing is disabled by default. If I enable directory browsing, however, it actually takes me to the actual directory listing rather than routing to the controller's index method.
I can move the Customers and Employees folders into a subfolder (i.e. move them out of the root) and everything works fine, but I'd like to try to keep these folders at the top level (per the Screaming Architecture guidelines).
Does anyone have a solution for this issue?
Please note that MVC Areas is not the solution I'm looking for as it does not allow for the folder structure described above (i.e. top-level folders named after high-level use cases and views contained directly within the Views folder rather than in a subfolder).

I'm betting you are right about IIS then. If you have two paths mapped to the same resource, the physical path is checked first on the IIS side.
I was digging around the routes configuration and found the property RouteExistingFiles on RouteCollection and think this could work.
I set the value to true and tested locally with an empty folder in the project, a route redirecting to Home/Index, and navigating to localhost:xxx/MyFolder. It worked correctly.
So then all you should need to do is set this property to true for it to choose Asp.net routes first instead of physical routes.

Related

ASP MVC 5 - Partial View File Outside of Views Folder

I have a partial view file which receives a #model saved outside of the views directory - actually stored in app_data.
However I get error:
The name 'model' does not exist in the current context
Source File: ...\app_data\_DefaultLayout.cshtml
Below is my code:
#Html.Partial("~/app_data/_DefaultLayout.cshtml"), Model)
Default Layout is as follows:
#using System.Web.Mvc
#using System.Web.Mvc.Html
#model MyApp.ViewModels.CreateCaseViewModel
// some html and razor code
This exception is a compilation error on the cshtml file. The view is not having access to model type. If you check your Solution Explorer, there are two web.config files, one in the Views folder and one at root level.
One way to sort it is making a copy of Views/web.config into App_Data.
App_Data is a special directory in ASP.NET, which is supposed to store data files like XML files / mdf files to store data. It has restricted access as well.
You should not be putting any UI layer concerns( views) in that folder. Asp.NET MVC has a convention for your UI layers, which is ~/Views directory. So use that location to keep your views.
If absolutely needed, you can put your view files pretty much anywhere in the application (Except those system directories), For example, you can create a directory called MyPartialViews and put your partial view there and refer the full path to that. The important thing to remember is, you need to have a web config in that folder (copy the one from the ~/Views and put it there). The web config has many needed elements in that including pageBaseType for all views

Asp .Net MVC 5 Folder name and Routing conflict

I have an annoying problem with folder names and routes in ASP .NET Mvc 5,
This is the default routes I'm using:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Site", action = "Index", id = UrlParameter.Optional }
);
And I'm also using a custom view engine to map views to the root folder instead of ~/Views so I can have HTML/CSS/JS files organized in a way my team can handle.
The problem is: When I call /Backoffice/Index it goes normally and executes action Index in controller Backoffice, all fine, but when we call /Backoffice, I expected it to presume the action index (by the route configuration), but instead, IIS seems to believe I'm trying to access the folder /Backoffice and gives me a 404 error instead of executing Backoffice::Index().
How can I configure IIS to behave in the intended way in this case? Or, is it really the only best way to keep views in a specific folder?
I did some research and used info from the comments and answers here and got to learn something about how MVC routes work and how IIS handles that.
When we set
routes.RouteExistingFiles = true;
The MVC runtime will ignore every file in the folder and start using the routes and URIs solely to look for controllers and actions, this means by calling /SomeContent it will search for a controller named Somefolder instead of a folder. Then we can configure to serve static content by telling MVC runtime to ignore some specific URI formats:
routes.IgnoreRoute("SomeContent/CSS/{filename}.min.css");
routes.IgnoreRoute("SomeContent/JS/{filename}.min.js");
This causes MVC to ignore URIs that match this pattern and leave it for IIS to resolve what to do with it, then IIS will look out on Web.Config rather there are configurations set to serve this kind of static content and what handler to use and proceeed as usual.
Using this configuration can bring MVC to a whole new level of control where you explicitly define which URI patterns serve static content, everything else explicitly calls an action on a controller, all URIs get to be firstly processed by MVC runtime. I sure wish someone correct me in this last statement if I get it wrong.
MVC routes certain actions based on whether they exist on disk. If you have a folder /BackOffice at the root level, then this appears to be a complication that MVC is going to have issues working around (I knew files were directly routed if they existed; I didn't realize folders were something the framework checked too). Consider renaming the folder or the controller to something else so you don't have this naming conflict. That is a problem with "by convention" approaches....

Actionless URL produces HTTP 404 (Controller & Folder Name Clash)

Problem
I have two controllers: RequestsController and ServicesController. Both contain Index actions.
When I browse to /Requests, it automatically runs the Index action, but for /Services or /Services/, it gives an HTTP 404 without even running the Index action.
Background
The route configuration is stock. The project also contains various classes under a top-level directory called Services:
Troubleshooting
The problem seems to be related to some sort of clash in naming between ServicesController and the top-level Services folder.
I can still access /Services/Index without a problem.
Debugging confirms that the Index method is being run for Requests but not Services when I don't specify the action name in the URL.
Renaming or removing the top-level Services folder causes the problem to stop happening.
I see. That's a common issue when you have a controller with the same name as a folder inside the root directory. Assuming that none of the files inside the Services folder are mapped by the StaticFilesHandler I think you can simply tell the routing system to map all "services" routes to the ServicesController by setting the RouteExistingFiles to true...
routes.RouteExistingFiles = true;
Update
I completely ignore the Content folder. You will need to prevent requests to the "Content" folder (or any other folder containing static files such as the Scripts folder) from going through the routing pipeline by explicitly specifying it BEFORE your routing-mapping logic...
routes.IgnoreRoute("FOLDER_NAME/{*pathInfo}");

Add subdirectory for locale based URL existing ASP.NET website, resolve relative paths correctly

Existing ASP.NET (MVC and webforms hybrid) website displays translated content. The language is based on a cookie that stores the user's preference. There is no change in the URL when the user changes the setting. The content is reloaded in the preferred language. For SEO, the locale should be included in the URL ( support.google.com/webmasters/answer/182192?hl=en).
I've tried the following:
1) Use URL Rewrite Module: (http://www.iis.net/learn/extensions/url-rewrite-module/setting-http-request-headers-and-iis-server-variables)
Issues:
- All hyperlinks and redirects still point to the old URL without the locale.
- Complex outbound rules required based on the folder structure and usage (mixture of absolute paths and relative paths e.g. ../, ~/, /).
- Also need to disable static compression as per documentation
- Performance considerations due to large size of Html.
- Postback results in runtime exceptions due to issue in the relative path rewrite.
- Paths defined in script files (ajax loading etc) are a huge challenge
- Base tag does not work as expected, because the Rewrite Module seems to append ../ (http://www.iis.net/learn/extensions/url-rewrite-module/url-rewriting-for-aspnet-web-forms#Using_tilda)
2) IIS 7.5 Virtual Directory: Create Virtual Directory for each language and point it to the root. i.e. www.example.com is the root and www.example.com/fr-ca/ is a virtual directory mapped back to the root
Issues:
- Runtime exception in config file saying that the virtual directory needs to be converted to application
- Converting it to application gives 500.19 error due to duplicate entries in the web config (since the virtual directory is pointing back to the root)
- I tried moving the root to another subdirectory (i.e. have a physical directory for each language) to avoid web config conflicts, but that is resulting in some sort of "kernel" error. Also, this would mean changing the physical structure of the application, and also address routing issues
3) Using sub-domains:
I have also considered using sub-domains and hosting the application independently for each language, but this has a lot of drawbacks, including having to address scalability, single sign on, cookies, domain specific stuff like analytics etc.
So what is the least painful way to include a language sub-directory in the URL, and make all links relative to that sub-directory?
Note: The site contains a mixture of absolute paths and relative paths e.g. (../, ~/, /) sometimes used in conjunction with ResolveClientUrl, ResolveUrl
In the end, we went with option 2, with the below steps:
Create a new folder, deploy a copy of the application to the new folder. The new folder should be in a different directory from the root application.
Create a new virtual application* (not virtual directory) under the root application; 1 for each new language, pointing to the new folder. (If the need arises in the future, any of the virtual applications can point a different folder customized for that specific language)
In the new folder, remove the modules and handlers sections in the system.webServer section of the web.config file (they will be inherited from the parent web.config)
If you are using SQL session state, you will need to specify a custom Application Name in the web.config, and modify TempGetAppID stored procedure so that the Application Name is the same across all the virtual applications. See the following (http://blogs.msdn.com/b/toddca/archive/2007/01/25/sharing-asp-net-session-state-across-applications.aspx)
Hopefully, all the links are resolved on the server side using Url.Content (MVC) or ResolveUrl (webforms). If not, they need to be fixed. Any paths specified in javascript would not automatically resolve to the virtual application either (they would still be resolved to root application)
Test the heck out of it. Each and every link. (A tool like ScreamingFrog may help to make sure that no 404s are returned, methinks. But it wouldn't solve HTTP POST)
Note that depending on custom error handling, and any existing URL rewrite rules, the steps maybe different.
Summary: option 1 (URL Rewrite) is totally impractical. Option 2 (sub-directory) is the most practical solution, however it is not quite as straightforward as it should've been.

ASP.NET - Response redirect to different folder

I am attempting to redirect a gridview on selection. However, I am getting stuck on redirection when the page I am trying to redirect to is in a different folder.
The gridview is in a folder named HR. I am trying to redirect this to a file called Staff within a folder called Staff (Staff\Staff). How can I redirect to a different folder?
If e.CommandName = "Select" Then
'Add to session variable; translate the index of clicked to Primary Key
Session.Add("DetailsKey", GridView1.DataKeys(e.CommandArgument).Value.ToString)
Response.Redirect("staff\staff.aspx")
End If
Response.Redirect("~/staff/staff.aspx")
The main thing is to use / rather than \. You aren't redirecting to a folder on the server, but to a path on the website (the fact that this means a folder on your server is just an implementation detail).
You can do all the forms that you can with relative links. Hence "staff/staff.aspx" goes to the file called staff.aspx in the folder called staff that is in the current folder (assuming your folder-and-file based system). "../staff/staff.aspx" goes up a folder, then to staff then to staff.aspx. "../../staff/staff.aspx" goes up two first. "/staff/staff.aspx" goes to the root of the domain on (http://mysite.com/staff/staff.aspx, etc).
As well as all of these, "~/staff/staff.aspx" goes to the root of the application, then to staff within that, then to staff.aspx. This is useful if you work on the site such that this would be in http://localhost/currentProject/staff/staff.aspx because the project is at http://localhost/currentProject/ but deployed to http://mysite.com/staff/staff.aspx as the site is at http://mysite.com/. This way the same code works both ways.
This should do the trick
Response.Redirect("~/staff/staff.aspx");

Resources