I have an Asp.Net MVC 3 website written in VB.Net. We want to migrate to C#, the plan being to write all new code in C#, and rewriting old code as and when it's amended.
I created a new C# MVC 3 project in the solution. Within this project I am using Razor Generator to help compile the views into the resulting assembly. I reference the C# project from the VB one. The VB project runs as the main site.
This set up works 90% beautifully. However, if an Area already exists in the VB project, I can't seem to extend it in the C# project. It appears that the whole Area has to exist in either one project or the other.
Does anyone know if it is possible to serve 1 area from 2 projects?
I had to work around this by creating a route in the area registration of the VB.Net project.
The Area Registration file in the C# project needed to be removed, and the route in the VB.Net project uses a slightly different URL pattern. When creating the new route the Namespace that the C# controllers reside in need to be set. The new route also needs to be declared before the default route for the area.
It is entirely possible, and what you are doing is correct. I would surmise that your routes are not set up correctly. You will have to specify the namespaces in the routing in your VB project.
In your AreaRegistration code:
Public Overrides Sub RegisterArea(ByVal context As AreaRegistrationContext)
context.MapRoute(
"Users_default",
"Users/{controller}/{action}/{id}",
New With {.action = "Index", .id = UrlParameter.Optional},
New String() {"MyCompany.MyAmazingCSProject.Areas.Users.Controllers",
Me.GetType().Namespace}
)
End Sub
Remember that VB namespaces have the added complication of root namespaces, which work differently to the Default namespace of C# projects, so if you're working with both, you need to be consistent.
i.e.
A VB project with a root namespace of MyCompany.MyAmazingVBProject has this namespace in code:
Namespace Areas.Users.Controllers
which maps to MyCompany.MyAmazingVBProject.Areas.Users.Controllers, whereas the equivalent namespace in C# would have this code:
namespace MyCompany.MyAmazingCSProject.Areas.Users.Controllers { ... }
Related
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
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).
I just implemented MVCContrib's Portable Area feature and it works fine. I can open it using:
http://localhost/projectname/portableAreaName, but this portable area is not working if i render it using the HtmlHelper extension method like this:
public static void RenderHtmlWidget(this HtmlHelper Html)
{
Html.RenderAction("Index", "HtmlWidget", new {area = "HtmlWidget"});
}
And calling the helper method in the view as such:
#using Project.Widgets.HtmlWidget;
#{Html.RenderHtmlWidget();}
I'm getting an error: The view 'Index' or its master was not found or no view engine supports the searched locations. In the possible location list there are no ~/areas/... defined.
But I can render my HtmlWidget successfully with this the same line of code in the view:
#{Html.RenderAction("Index", "HtmlWidget", new { area = "HtmlWidget" });}
What am I doing wrong and how should I use the HtmlHelper extensions correctly with the MVCContrib portable areas feature?
There are a few things that may be causing this.
In the calling/parent project where you use the helper method to invoke your portable area, do you have a Web.config file in the /Areas/ folder? If not, you must copy the Web.config found in the /Views/ folder of the same project, and simply place the new copy in the /Areas/ folder as well.
In the Registration class file in your portable area project, after you call MapRoute in the "RegisterArea" method, are you calling "RegisterAreaEmbeddedResources();"?
Is each view in your portable area project made to be an embedded resource as opposed to content? Select a View in the Solution Explorer and hit F4, "Build Action" should be set to "Embedded Resource", but it defaults to "Content"
You also need to make sure that both the Portable project and the consuming project reference the same version of MvcContrib, but that they also utilize the same version of ASP.NET MVC. If your area is referenced in multiple projects, each based off of a different version of MVC (not likely, but possible depending on the situation), your area must use whatever version of MVC the consuming project uses.
I'd also suggest using Phil Haack's .NET Routing Debugger - its a single DLL file that you reference in the consuming application and add a single line to your ApplicationStart() in your Global.asax.cs. This becomes incredibly helpful in determining if your portable area is being correctly registered with the base project - and helps you cut to the chase.
I would like to be able to reuse an area in one of my MVC project in multiple MVC projects. Can I put my area in a class library that can later be reused in a new MVC project?
Thanks,
Not out of the box. You may take a look at MVCContrib Portable Areas which use a custom VirtualPathProvider allowing you to embed areas as resources into separate assemblies. If you are using ASP.NET MVC 3 you could also take a look at the following blog post.
Be careful though as this might not work if you precompile your web application.
You can have the AreaRegistration class and the Controllers in a library. The ASP.NET MVC runtime will register them automatically.
But the the views need to be in any of the following locations:
~/Areas/[AreaName]/Views/[ControllerName]/[ViewName].cshtml
~/Areas/[AreaName]/Views/Shared/[ViewName].cshtml
~/Views/[ControllerName]/[ViewName].cshtml
~/Views/Shared/[ViewName].cshtml
Or you can specify them yourself by setting them when registering the View Engine:
var viewEngine = new RazorViewEngine {
AreaViewLocationFormats = new[] { "~/MySharedAreas/{2}/Views/{1}/{0}.cshtml" }
};
2 is area name, 1 is controller name and 0 is action name.
It depends on the purpose. If you just want to reuse the controllers this method will work, but if you want to reuse the Views as well, then you have to go with the solution mentioned by Dimitri
I'm using VS2008 and .net 3.5. I have created a class library(Myproject.Controllers) in my solution. Under this class, I have added a Controllers folder. And in the folder I have added a MyController which is declared as
public class MyController : Controller
My views are still in the default Views folder. Now, when I run this in VS, I get a message in the Default.aspx.cs:
{"The controller for path '/' could not be found or it does not implement IController."}
If I put a copy of my MyController in the default Controllers folder then it works fine. Does anyone know how I can set/configure the Controllers path? I've searched the web and didn't find anything for this. Thank you.
By creating your own Controller factory, you can specify exactly how controllers are used. Here's how
step 1 - create a class and derive it from IControllerFactory
http://msdn.microsoft.com/en-us/library/system.web.mvc.icontrollerfactory_members.aspx
step 2 - Consume your new Controller Factory in application start
ControllerBuilder.Current.SetControllerFactory(typeof(MyControllerFactory));
IControllerFacotry implements 2 methods
IController IControllerFactory.CreateController(System.Web.Routing.RequestContext requestContext, string controllerName);
void IControllerFactory.ReleaseController(IController controller);
And that's all there is to it.
I did this not too long ago, and the only tricky part was to make sure that the namespace where the controller resides is the same as it would be if it was in the default folder. The main pitfall is the root namespace of the class library project - you can change it by right-clicking the project node in Solution Explorer, selecting Properties and changing the value in the Root Namespace textbox. The easiest way is to name it the same as the MVC application itself.
From my understanding, this should just work. Did you add your Controllers library as a reference to your web site?
From MVC source
// ControllerTypeCache.cs:
private static List<Type> GetAllControllerTypes(IBuildManager buildManager) {
// Go through all assemblies referenced by the application and search for
// controllers and controller factories.
// DefaultControllerFactory.cs
// if all else fails, search every namespace
return GetControllerTypeWithinNamespaces(controllerName, null /* namespaces */);
I had a similar issue using dotnet 4.0 utilizing MVC 2.0 on IIS7.0. After checking all the normal stuff, having the right MVC assembly version and so on, i finally moved the code from a random location on the web server to /inetpub/www and recreated my site on the webserver. That seemed to have done the trick. Aparently, DotNet MVC likes to have the source under /inetpub/www to be able to resolve the MVC paths.