Deploying un-compiled ASP.NET MVC Razor application - asp.net-mvc

The question "https://stackoverflow.com/questions/16397785/add-pages-to-mvc-deployed-website" seems to relate closely to the question I want to ask. But it my case, I have not yet developed an application, but I want to develop it in such a way that it can be customized by users after it is deployed by adding files to the deployed directory. What would be the advisable way of doing this? Specifically, I want users to be able to define custom pages, possibly replace existing pages or add controls to existing pages, and possibly define custom WebAPI functions to retrieve custom data. I tried adding .vbhtml files to the Views directory as described, but ran into the same problem described in the linked question.

Razor views are compiled at runtime, so you always deploy them un-compiled.
What you want is simply Razor pages, so can add them to any directory (not Views) and access them without the file extension (e.g. /Foo/Bar instead of /Foo/Bar.vbhtml).

I don't think I would recommend using the filesystem for this. I think you should save the razor code to the database and then, when you wish to parse it, you may do so according to this Using RazorEngine to parse Razor templates concurrently
Here is another example:
var model = new { Name = "Test" };
var template = "Hello #Model.Name";
var result = Razor.Parse(template, model);
Code taken from Using Razor engine on strings - not views
Edit to answer your questions:
The razor code would be stored in the database along with whatever controller you wanted to run it. When you retrieve the razor code from the database, you would also know the controller, then you could redirect to that controller, sending whatever model you want, to the razor code, and then parse it as shown above. Make sense?

The routing configuration can be customized to identify a URL pattern that identifies all requests for customized code and route them through a common controller that can then load arbitrary views based on information provided in the URL. Notice the route with the name Custom below, and how it always uses the Index action of a controller named Custom, but introduces a new parameter customization.
Public Class RouteConfig
Public Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.MapRoute( _
name:="Custom", _
url:="custom/{customization}/{id}", _
defaults:=New With {.controller = "Custom", .action = "Index", .id = UrlParameter.Optional} _
)
routes.MapRoute( _
name:="Default", _
url:="{controller}/{action}/{id}", _
defaults:=New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional} _
)
End Sub
End Class
Once this is set up, add a controller called CustomController to the project that simply passes through to a view determined by the customization parameter:
Public Class CustomController
Inherits System.Web.Mvc.Controller
'
' GET: /Custom
Function Index(customization As String) As ActionResult
Return View(customization)
End Function
End Class
Publish this code and add a Custom directory under the Views directory in the deployed location. Now you can put any .vbhtml file in the Custom folder and refer to it with a URL like http://localhost/MyApplication/Custom/MyView, which will load MyView.vbhtml from the Views\Custom directory.

If the main intent is to provide extensibility, then there is a simpler answer. ASP.Net will probe all assemblies in the bin directory of the deployed web application and pick up all controllers and models in compiled assemblies there, as well as all un-compiled views in the Views directory. With some effort I was able to determine a minimal set of files necessary to create an independent template project that could be used by people who would develop and deploy custom code into the running (deployed) web application. The details are provided as an answer to a more relevant question I discovered on this topic because it was not straightforward to get Intellisense and other ASP.Net MVC4 apsects of this template project working. See https://stackoverflow.com/a/21122377/78162
Since Visual Studio Express is now available for free, my hope is that such a template project could be loaded as a starting point for developing customizations to another ASP.Net MVC4 application. My limited testing indicates this will work for both the UI layer (as demonstrated in previous link) and the API layers (as discussed at https://stackoverflow.com/a/21028825/78162).

Related

Organizing (splitting?) controller while maintaining URL

We have a large web application that is a mix of WebForms and MVC. The URLs we work with look as such www.something.com/Secure/Module/SomeAction. When we used to work with Webforms Module was a folder, as we had as many ASPXs as we needed under that folder. With MVC we're taking a similar approach, where Module translates to a controller, containing all the action. The problem that we're running into is that if the Module has something like 20 - 30 actions it's getting really messy. For example if we have a PersonReport, that usually translates to several actions dedicated to serving that report alone (to facilitate ajax calls etc). Our actions are fairly thin, they populate the model (usually calling WCF services) and that's about it. Nevertheless it can easily creep up to 1500 lines of codes, and we start utilizing regions to organize sections of the controller. Creating a new controller (by default) will obviously stray away from our URL pattern. Any suggestions on how to better organize this mess?
You can use attribute routing if you plan to use MVC 5. This will allow you to specify custom routes for each Action method with attribute
The previous versions allow you to override the default routes through RouteConfig.cs file. Example would be
routes.MapRoute(
name: “ProductPage”,
url: “{productId}/{productTitle}”,
defaults: new { controller = “Products”, action = “Show” },
constraints: new { productId = “\\d+” }
);
See this link for more info.
Hope this helps.

How to implement my own folder structure(architecture) in ASP.NET MVC Project?

As we know, when we create an MVC application, it creates its own typical structure which is known as convention over configuration and its a good practice .
It configure views, controller and model separately .
My concern is, can i architect(design) it like :
If I do that, My viewengine will search views inside view not inside subfolders and there are so many things like routing will get changed.. and so on..
Actually I dont want to construct my view,controller or model in a typical way, I want to put my view separately according to my domain, not according to controller like MVC does.
However in case of controller we can use any folder structure . I am specific about model,views and routing should not be affected as well.
And it is all about "Convention over My own Configuration".
Can someone please explain, how to get it done or any other alternatives.
Thanx
Anupam.
It sounds like what you are looking for is 'Areas'. This allows you to separate your controllers & views into separate 'area' folders.
More information can be found here, as including the necessary information to get this set up in this answer is probably not practical:
http://msdn.microsoft.com/en-GB/library/ee671793(v=vs.100).aspx
The location and folder structure of your controllers and models doesn't really matter, should work either way. Controllers are located by their type and classname.
The viewengine by default does search subfolders, trying to match with the naming of your controller. It searches multiple locations.
Now, if you want to change how the view engine searches for files you can configure it in global.asax. Have a look here regarding RazorViewEngine for example.
Personally I have gone away from the view engine auto locating my views and instead use relative paths for all of them because I think it makes it more readable overall.
Below is an example of a configured view engine and a relative path.
global.asax
ViewEngines.Engines.Clear();
var razorEngine = new RazorViewEngine() { FileExtensions = new string[] { "cshtml" } };
ViewEngines.Engines.Add(razorEngine);
controller action
return View("~/Views/Home/Index.cshtml", model);
Hope I understood your question correctly.
So far I have reached to conclusion.
The thing we have to consider for this are :
1.Need to override ControllerFactory : we have to search controller in specific location or assembly reference.(by default controller factory just chak the controller name )
2.Need to override ViewEngine : We have to change view search location according to our need.
3.Little modification in Route : we have to specify module name in routs for proper redirection.
route will be something like :
routes.MapRoute(
name: "Default",
url: "{module}/{controller}/{action}/{id}",
defaults: new { module = "HR", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
will implement it soon. Your suggestions are most welcome.

Sitecore multisite MVC solution

I'm new to Sitecore MVC and currently with web forms I have all the sites organized under:
\Website\Sites\Site1\css|js|Layouts|Sublayouts|etc.
\Website\Sites\Site{n}\css|js|Layouts|Sublayouts|etc.
I'm able to add an MVC site to my solution and works fine alongside the web forms sites; however, adding a second MVC site that happen to have the same controller/view names generates a conflict.
For example, if I create a controller for Site1
Controllers/Site1/FooController (has index and hello)
Then the views are:
Views/Foo/Index
Views/Foo/Hello
But if Site2 also has a controller with the same name then it's a conflict:
Controllers/Site2/FooController (has index and hello)
Then the views are:
Views/Foo/Index
Views/Foo/Hello
But they're used by Site1.
The question is how to setup two (or more) MVC sites that happen to have the same controller/view names. Is there a recommended way to structure the sites in the solution or do I have to override pipelines/processors?
Thanks
Update:
Thanks everyone. Areas solved my problem but introduced two new problems:
The conflict in the controller names which solved by putting the namespace, class and dll names in the controller name in Sitecore - reference: http://blog.xcentium.com/2014/03/sitecore-mvc-and-duplicate-controller-names/
When the controller returns a view, I have to put the full path of the view; otherwise, I get an error where the view is not found.
For example: return View("~/Areas/Site1/Views/Home/Index.cshtml");
I'm looking into a fix provided from a developer from Sitecore's forum:
http://www.chrisvandesteeg.nl/2014/06/13/sitecore-mvc-in-a-multisite-environment-areas/
I'll try it out and report back.
you need to use namespaces in routes.MapRoute, look at the below posts which have already discussed:
Is it possible, in MVC3, to have the same controller name in different areas?
Multiple MVC projects in a single solution
and below is the post by John west post which relates your situation:
http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2012/06/Using-Web-Forms-and-MVC-in-a-Single-Solution-with-the-Sitecore-ASPNET-CMS.aspx
We had the similar problem and answer was to separate out every site with MVC areas and they works perfectly. Though we ran into issue of controller name duplication but that can be resolved by adding the namespace during the area route registration.
But a clean way to implement this is to let Sitecore know about the MVC areas and initialize your controller/action with area and namespace. This process has been blogged by Kevin and he has a package as well. It expect you to define the area name in controller rendering.
http://webcmd.wordpress.com/2013/01/24/sitecore-mvc-area-controller-rendering-type/
To avoid the hard coded path of view(s) you can always extend controller rendering template to add view path and create an action filter to add the view path after action is executed. Add the below code in action filter and register the filter in sitecore action filter registration pipeline.
public void OnActionExecuted(ActionExecutedContext filterContext)
{
ViewResult result= filterContext.Result as ViewResult;
if(result == null) return;
Rendering redering = RenderingContext.CurrentOrNull.With(x=>x.Rendering).Return(x=>x,null);
string viewName= rendering.Return(r=> r.GetFieldValue(CustomMvcSettings.ViewPathField), string.Empty);
if(String.IsnullOrEmpty(viewName)) return;
result.ViewName = viewName;
}
The best thing you can do is split your websites up in different projects in the same solution.
Building two websites in the same project can become unstructured and messy.
After that you can route the controllers with the same name using the different namespaces.
Sitecore mvc duplicate controller
Just to keep this topic in sync with the SDN forum,
I recommend using a sitecore specific constraint, as described at
http://www.chrisvandesteeg.nl/2014/06/13/sitecore-mvc-in-a-multisite-environment-areas/
This solution allows you to set the attribute mvcArea on your configuration node

Serving HTML or ASPX files with ASP.NET MVC

I want to implemented URL like :
www.domain.com/Business/Manufacturing/Category/Product1
This will give the details of specific product(such as specifications).
Since the list of Categories & Products in each Category are limited, I thought it is not worth to use database for products.
So I want to implement it using the HTML or ASPX files. Also I want the URL to be without the files extention(i.e. URL's should not have .html or .aspx extension)
How can I implement it with ASP.NET MVC? Should I use nested folder structure with HTML/ASPX files in it, so that it corresponds to URL? Then how to avoid extensions in URL?
I am confused with these thoughts
Asp.net Mvc uses the routing library from Microsoft. So it is very easy to get this kind of structure without thinking about the folder structure or the file extensions. With asp.new mvc you do not point a request at a specific file. Instead you point at a action that handles the request and use the parameters to determine what to render and send to the client. To implement your example you can do something like this:
_routes.MapRoute(
"Product",
"Business/Manufacturing/Category/Product{id}",
new {controller = "Product", action = "Details", id = ""}
);
This route will match the url you described and execute the action named "Details" on a controller named "ProductController" (if you are using the default settings). That action can look something like this:
public ActionResult Details(int id) {
return View(string.Format("Product{0}", id);
}
This action will then render views depending on what id the product have (the number after "Product" in the end of your example url). This view should be located in the Views/Product folder if you use the default settings. Then if you add a view named "Product1.aspx" in that folder, that is the view that will be rendered when you visit the url in your example.
All tough it is very possible to do it that way I would strongly recommend against it. You will have to do a lot of duplicated work even if you only have a few products and use partial views in a good way to minimize the ui duplications. I would recommend you use a database or some other kind of storage for you products and use a single view as template to render the product. You can use the same route for that. You just edit your action a little bit. It can look something like this:
public ActionResult Details(int id) {
var product = //get product by id from database or something else
return View(product);
}
This way you can strongly type your view to a product object and you will not have that much duplication.
The routing engine is very flexible, and when you have played around with it and learned how it works you will be able to change your url in just about any way you want without changing any other code or moving any files.
If you're not ready to dive into ASP.Net MVC, you can still get the nice URLs by using URL Rewriting for ASP.Net. That'd be simpler if you're already familiar with ASP.Net WebForms. The MSDN article on URL Rewriting should be a good start:
http://msdn.microsoft.com/en-us/library/ms972974.aspx
I'd be really sure you won't eventually have more products before deciding not to use a database. Both MVC and WebForms would allow you to make one page that dyamically shows a product and still have a nice URL- plus you might save yourself development time down the road. Something to think about.

ASP.NET MVC basic routing with no View name

This is a basic question about the routing machinery. From a new MVC project, I have this in the HomeController:
public ActionResult MyPage()
{
return View();
}
In the Views\Home folder, I have MyPage.aspx. The routing is still the default of {controller}/{action}/{id}. When I type in http://localhost:1790/Home/MyPage, it is correclty routes to MyPage.aspx. Since I haven't said anything about which view to render, how does ASP.NET MVC know to correctly route to MyPage.aspx? Looks as though the ActionResult name can also be used as the View/aspx page name...unless there is something I misunderstand in how the routing works. I can see how I end up in the Home folder, since the controller name corresponds to the View sub folder name. But does the Action name also correspond to the aspx name?
Would that work if the page was a PHP?
ASP.NET MVC subscribes to what is known as the Convention over Configuration paradigm whereas if you follow their conventions, basic things such as routing concerns will happen for you. But they also allow you to configure them if desired.
MVC implicitly assumes that if you return just View(), that you want View("MyPage") (i.e. the action name). No sense in repeating yourself unnecessarily.
It won't find a PHP file by default, but I'm sure you could override that behavior if you really wanted to. I can't imagine a sane scenario where you would be mixing PHP and ASP.NET MVC, but who knows :)
Action name is the same as the view / partial view name.
asp.net mvc doesn't work with php as far as I'm aware.
As has already been stated, ASP.NET MVC uses convention over configuration. Out of the box, your folder structure is something like this (only showing relevant portions and doing it from memory so...)
Site Root
+ Controllers
HomeController.cs
AccountController.cs
+ Views
+ Home
Index.aspx
+ Account
Index.aspx
+ Shared
The default routing handler is something similar to the following:
"{controller}/{action}/{id}"
There are default values for the route, but if you have a url that is a/b/c, it will look for action a on controller aController and pass it c as a parameter if said method on the controller accepts parameters.
A couple of things about that then need to be clarified. Again, convention over configuration:
1) All controller classes must end with Controller if you're using the default engine. That way, when a request comes in and the {controller} value is parsed, the engine adds Controller to it, looks in the Controller folder (and, thus, namespace), and locates the class.
2) By default -- this can be changed -- all views for a controller must reside in the Views/{controller} folder or in the Views/Shared folder.
3) Public methods on a controller are, by default, actions. You can hide this with an attribute to make themethod unavailable to the engine, but by default they are public.
So, when a request comes in the route is compared against all known routes (global.asax) and the first route that matches the request is accepted. The route is then parsed into the component parts to determine the controller, action, and parameters for the action.
Once the controller is identified, the engine instantiates an instance of that controller and executes the matching method (action) should it be found.
The action will return an ActionResult. View is an extensino method that actually returns ViewResult (if I remember that correctly). The default view for an action is a view of the same name as teh action residing in the Views/{ControllerName} folder.
Routing is a beast unto itself and I'd recommend a good bit of reading on it if you're going to play with it. Minutes to understand but a lifetime to master sorta thing.
To my knowledge, BTW, there is no engine that will use a php page as a view for a controller action.

Resources