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.
Related
I can't reuse my cshtml files from another assembly. Here's the bare-bone sample:
Create an ASP.NET Core Web Application project with default template (using Web-Application, Model-View-Controller), and name it ViewReuse
Add a class library called ViewLibrary
Add a reference to Microsoft.AspNetCore.All metapackage in ViewLibrary
Create a folder called Views, then create another folder called Shared, and inside it create a simple cshtml file called ReusedLayout.cshtml
Add EmbeddedResources Include='Views\**\*.cshtml' to csproj of ViewLibrary, to include all views inside the ViewLibrary.dll
In ViewReuse project, inside Startup.cs, change configuration of MVC service to services.AddMvc().ConfigureApplicationPartManager(p => { p.ApplicationParts.Add(new AssemblyPart(typeof(ReusedController).Assembly)); });
Change About.cshtml to use the layout from ViewLibrary: Layout = "/Views/Shared/ReusedLayout.cshtml"
Then run the application, and navigate to /home/about.
For me I encountered this error:
InvalidOperationException: The layout view
'/Views/Shared/ReusedLayout.cshtml' could not be located. The
following locations were searched: /Views/Shared/ReusedLayout.cshtml
What have I done wrong? How do I solve this issue?
I solved it by the following way:
Razor class library project
First I created a solution called RazorDll with a Razor dll library project and deleted everything in it. Now create the MVC structure for the views. For testing purpose, I added a file called _Layout2 there.
Also add an empty Startup class in the project root for assembly detection later:
namespace RazorDll {
public class Startup {
}
}
Its important to have all Razor filed embedded! You have two ways of doing this:
Embedd all in the .csproj file properties
If you have a project just for embedding all Razor view, this is the best option since you don't have to edit file propertys for every view by hand. Just right click on the project and choose edit project file, or open the corresponding .csproj file with any text editor and insert the following in <Project>
<ItemGroup>
<EmbeddedResource Include="Views\**\*.cshtml">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
Embedd single Razor views by hand
Just right click on a Razor views .cshtml file, choose properties and set the build action to embedded ressource. Required for every view you want to use from another .NET Core assembly.
Consuming MVC project for the Razor class library
Now create the MVC project, called MvcDemo here. This is a normal ASP.NET Core 2.1 MVC project (choosed the LTS here, but should also work with 2.2) that got linked to our Razor dll
To search for Razor views in the Assembly, we add it in Startup.ConfigureServices method:
var viewAssembly = typeof(RazorDll.Startup).GetTypeInfo().Assembly;
var fileProvider = new EmbeddedFileProvider(viewAssembly);
services.Configure<RazorViewEngineOptions>(options => {
options.FileProviders.Add(fileProvider);
});
Notice the full qualified type in typeof to distinct between the Startup class of the consuming MVC project (which would be loaded without namespace) and the Razor class library.
You're ready to change e.g. _ViewStart.cshtml to consume our test _Layout2 from our Razor class library:
#{
Layout = "_Layout2";
}
Result of a simple POC demo project:
Try right-click over the view files and in properties change to embedded resource.
I added a default MvcApplication (MVC 4) (its name is MvcApplication3 matching the name of my solution's) width Home views (About, Index, Contact) and that will be my startup (bold in VS solution explorer's interface) project. Then I added another project (MvcApplication, but this time an empty one) called MvcApplication2 to the solution. Then I added the latter project as a reference to the first. I also added a controller called TestController (green line) to the referenced project and generated a view for its Index (red arrow) method. However, when I go to a link /Test or /Test/Index, the view I am expecting (red arrow) is not shown. Then I added the same folder Test with Index.cshtml (blue arrow) to the main project and now I am seeing its contents rather than the project's where my controller sits in.
Is it possible to make the application look for the views in the other project rather than the startup one?
I am adding the image of the structure to make it easier to follow.
P.S.: probably related: the breakpoint IS being hit in the Index method of TestController.
tldr; blue view is used instead of a red one
I think your problema is that you have set MvcApplication3 as startup Project and is causing you to open the view off that Project.
Is it possible to make the application look for the views in the other
project rather than the startup one?
Yes its posible, you can redirect your application the url. Think this your application have a url http://localhost:(someport) you can set redirect to the port of the second application.
I put a link to for better understanding a routing system of MVC: Documentation of routing system
As far as I know, you can't link to projects together like that. Each project becomes its own website with its own address. The reason you might put multiple projects together in one solution is to share things like classes, services, etc. I think what you're needing is areas:
http://www.codeproject.com/Articles/714356/Areas-in-ASP-NET-MVC
With some ideas from me and a friend of mine and a link about overriding RazorViewEngine I finally got what I wanted working exactly how I was expecting it to:
I created a folder named ViewsBase in the main project.
I rewrote RazorViewEngine this way: I only changed the place that was needed for me, leaving everything else like I found in the constructor of RazorViewEngine:
public class MyCustomViewEngine : RazorViewEngine
{
public MyCustomViewEngine()
{
...
ViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml",
"~/ViewsBase/{1}/{0}.cshtml",
"~/ViewsBase/{1}/{0}.vbhtml"
};
...
(I find it rather disturbing how I am unable to format the code properly. Can someone give me a hand please?)
and in Global.asax of the main project I added:
ViewEngines.Engines.Clear();
var ourViewEngine = new
ViewEngines.Engines.Add(ourViewEngine);
AreaRegistration.RegisterAllAreas();
...
I added a post-build event command:
xcopy /s "$(ProjectDir)Views\*.*" /Y "$(SolutionDir)$(SolutionName)\ViewsBase\"
It started looking for the views in the expected order
I added a ViewStart file to the base project to make it render the Layout too.
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 { ... }
I've been working with ASP.Net MVC (3) for some time now and i like it a lot. But one thing i find a bit annoying is having to browse between the controllers / views / model / script directory all the time. So i'm wondering if there's a way to tell MVC to look for the files in a different location?
Maybe someone can tell me how to simply group the files together by controller like:
Directory: /Membership
MembershipController
LogOnView
LogOnModel
RegisterView
RegisterModel
Kind regards
Olav
I know exactly what you're talking about. Here are the conditions where I find the default MVC folder structure to be onerous:
I'm using a model-per-view approach
My controller basically only works with that one particular view
I have some javascript that only pertains to that view
Why do I want to put each of these pieces in a different folder?
I create a folder for the view in the Views folder, so you have a folder ~/Views/MyEntityList (just like the traditional MVC approach), but I put everything that pertains to that component there:
~/Views/MyEntityList/
MyEntityListController.cs
MyEntityListModel.cs
MyEntityList.js
MyEntityList.aspx
I find this structure leads all the developers to keep views decoupled from one another. No special MVC configuration is required, except for allowing browsers to access the .js resources directly.
There are some architectural patterns where this might not be a good way to go. For a model-per-view approach (see Los Techies for more description) I really like this structure.
I think you need to get the Solution Navigator extensions via Power Tools update for VS 2010.
That way, you can display in the Solution Navigator, as opposed to the solution explorer, only the open files, for example. Makes it easier.
By the way, delete all the model folders and create a separate model project, eg:
MyApp.Domain
Any solution that is beyond basic will benefit from this.
As stated in the comments to your question, Areas will also reduce your navigation requirements.
The only "looking of files" going on is with views, everything else is just a convention, so if you want you could have:
Directory: /Membership
MembershipController
LogOnView
LogOnModel
RegisterView
RegisterModel
... but the views must be in ~/Views/Membership
It looks like you have to override some behavior in the view engine. You can See this question to get a better idea.
One way I can think of to achieve this is by writing your custom view engine. You can place all these below files in Controllers/Membership
MembershipController
LogOnView
LogOnModel
RegisterView
RegisterModel
Models will not be a problem you can simply change the namespace for the models, the only problem is with the views. For this write your custom view engine so that your mvc application knows the physical location of the view files as follows.
public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine()
{
ViewLocationFormats = new[]
{
"~/Controllers/{1}/{0}.cshtml",
};
}
}
In global.asax.cs add the ViewEngine in Application_Start() by including the following code
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());
You may also have to take care of various other factors like updating the Layout attribute depending on where you place the _Layout.cshtml.
In case you are using areas, add the AreaViewLocationFormats string array as well.
You can do further customization by overriding some of the methods like FileExists, CreateView, CreatePartialView.
Note: Do not forget to copy web.config in the views folder to the Membership controller. Otherwise application does not find the required mvc namespaces and it does not find the symbols like viewbag, model etc.
I'm working with Visual Studio 2008 SP1 and ASP.NET MVC v1. When right clicking on a view I do not get the option "Convert to Web Application" that I would need to generate code behind .cs classes. I see that option for the actual project and folders, but not for views (aspx files). I've checked the ProjectTypeGuids to have the "right" (?) values:
{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
Any other suggestions as to what I could look for?
Thanks.
(I am aware of design implications of using code behind classes with MVC)
P.S. To do it manually all you have to do is:
Add a file with the same name as your view and the .cs (or .vb) extension, for example Index.aspx.cs. Make sure you modify your class to inherit from System.Web.Mvc.ViewPage or some other class that inherits from that.
Edit the aspx file and add to the #Page directive CodeBehind="Index.aspx.cs" and change Inherits to "MyNamespace.Views.Home.Index" (obviously you need to have the right code behind and namespace there).
Right click on the aspx file and choose Convert to Web Application. This will create the design file and also modify your .cs class and mark it as "partial".
"Convert to web application" is a project/file-level command. You can't use it on a single ASPX file.
Also, there is no alternative automated way (that I know of :-)) to add code-behind files to an ASPX file. You have to do it manually, by adding the relevant files yourself and then adding them to the .csproj.
There's no need to use 'code-behind' with ASP.NET MVC.
If you use a 'code-behind', you're not following the convention of ASP.NET MVC.
The question is, why do you want a code-behind? Answering that will help us to determine what you really need.
If you really want to do this, you can do it by mixing Webforms and ASP.NET MVC together. There are lots of resources on this, but here's just one.
The MVC development model does not need code behind.
Read a good Blog Post on this Here
If you're trying to reuse some controls, maybe a good approach is to create and render them inside a helper method and than call that method from the view.
What I'm thinking about would be something like this:
public static string HelperMethod(param_list)
{
var control = new ControlType();
//set up control properties according to param_list
//get the html as string - one way to do it would be like this
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlWriter= new HtmlTextWriter(stringWriter);
control.RenderControl(htmlWriter);
string result= stringWriter.ToString();
}
And then call it from the view like this:
<%= HelperClass.HelperMethod(params) %>
I'm not sure if this approach will work, I don't know even if it makes sense. It's more of I hack than a proper solution. I haven't done anything like this before, it's just an idea, try to see if it helps you. You should also have in mind that ASP.NET controls usually use the ViewState for state management and that there is no such thing in ASP.NET MVC.