-
Notifications
You must be signed in to change notification settings - Fork 2.1k
View Components in a separate assembly #3750
Comments
services.Configure<RazorViewEngineOptions>(options =>
{
var embeddedFileProvider = new EmbeddedFileProvider( typeof(BookOfTheMonkViewComponent).GetTypeInfo().Assembly);
var compositeFileProvider = new CompositeFileProvider(
embeddedFileProvider,
options.FileProvider);
options.FileProvider = compositeFileProvider;
} I haven't actually tested this out, so let me know if it fails in some awful way. |
@pranavkm where CompositeFileProvider is defined ? Does it included in RC1? |
I can't find https://github.com/aspnet/Mvc/blob/dev/test/Microsoft.AspNet.Mvc.TestCommon/TestFileProvider.cs |
The changes went in farely recently so you'd have to point to the |
Thanks you @pranavkm!
Thanks, |
My bad - corrected my code snippet. It should be |
Thank you @pranavkm. Everything compiles now, but unfortunately it still doesn't work. Here's the response I get on a page that contains a view component from another assembly:
When I copy the cshtml into the proper directory in my startup web project, then it works, so the view component .cs file is registered properly, but not the cshtml file. |
Can you turn on the developer exception page and include the exception stack trace? |
@pranavkm: I think this is what you're looking for?
Here's a screenshot as well in case it's useful: Here's my code in Startup.cs
|
Going by your image in the StackOverflow post, you would need to move the |
@pranavkm: thanks for noticing the error in my folder structure. I made the change and I added baseNamespace but I still get the same error.
Here's my updated fileprovider registration:
Updated folder structure: |
I also tried renaming the |
Are you embedding your views as resources? https://github.com/aspnet/Mvc/blob/dev/samples/EmbeddedViewSample.Web/project.json#L6 |
Wow, this must be my missing piece. Would I add this to my startup web project's |
I guess you should add in the class library's |
Added this to class library's
|
I take back what I said. I created a new solution with only the absolute minimum parts, and it works. I shared this solution on GitHub: https://github.com/johnnyoshika/mvc6-view-components Now I have to figure out why this technique doesn't work in my other project. :-( |
Very cool! |
@Morgma: I got page views from separate assemblies to load without any problem using the same technique as view components. I describe it here: http://stackoverflow.com/a/34366119/188740 And here's a working example: |
@johnnyoshika I had been working off your solution previously, but now revisiting it, I have found my issue. The baseNamespace is definitely required to make this work. Additionally, my approach to using ILibraryManager to find the assemblies doesn't work. My guess is that even though I reference the module in project.json and the assembly is found in library manager, the compiler never loads the assembly unless there is a reference to the namespace in a "using". I was hoping to get to a point where I could install the module NuGet and automatically handle everything I needed to essentially load a new "Area" within the main web project. Finally, for some reason, Visual Studio is working against me by intellisense declaring the usings unnecessary, though the solution builds fine despite the red and ultimately does work. This is even in the case of a fresh clone of your solution: Thanks for your code; helped me through these gotchas. |
Now that I've determined my root issue, though it was the first thing I discounted in my initial post, is that the defaultNamespace parameter was not set in my EmbeddedFileProvider. I was able to return to dynamically loading the assemblies based on the convention of starting with "Module" and all is working well. Thanks again for your help @johnnyoshika ! Here's an example of how I'm loading in all of my View providers: public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var libNames = _libraryManager.GetLibraries().Select(lib => lib.Name).Distinct();
var assemblies = new List<Assembly>();
var fileProviders = new List<IFileProvider>();
foreach (var libName in libNames.Where(libName => libName.StartsWith("Module")))
{
Assembly assm;
try
{
assm = Assembly.Load(new AssemblyName(libName));
}
catch
{
continue;
}
assemblies.Add(assm);
var defaultNamespace = assm.FullName.Split(',')[0];
fileProviders.Add(new EmbeddedFileProvider(assm, defaultNamespace));
}
services.Configure<RazorViewEngineOptions>(options =>
{
fileProviders.Add(options.FileProvider);
options.FileProvider = new CompositeFileProvider(fileProviders.ToArray());
});
} |
@Morgma: fantastic! |
Great solution, exactly what I was looking for. What about things other than views though. How would you access things such as js, css or image files in the wwwroot of the external assembly? |
The StaticFileMiddleware allows specifying a file provider. Perhaps specifying the CompositeFileProvider as it's file system view would suffice? |
@pranavkm In my case, the external assemblies are not standalone web projects with a wwwroot; just NuGet package class libraries. Within the same project source, I'm exposing a Bower package that will supply the static files. My core web app will use Gulp to provision these dependencies to the right locations and do any transformations (script ordering, namespacing, etc...) I'm still working through some implementation details, but seems to be a viable approach to getting to the level of isolation I need between modules. |
The idea would be to embed resources in the class library and register a static file middleware with a composite file system - similar to the way the composite file system for views is created., |
Thanks for the advice. I had tried the following in the Configure method to use a composite file provider for static files in app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new CompositeFileProvider(fileProviders.ToArray()),
}); But Im not sure how to then access the static files from client side code, whatever I try just results in a 404 error. Im not sure if im embeddingthe files as resources properly in my project.json or if im adding the file properly in my html. I will give @Morgma approach a try but I havent used Gulp before and was trying to get around having to write a load of scripts to pull the files in as this would mean possibly having to update those scripts when changes are made to the external assembly. I was hoping there would be a way to do this without using scripts to bring the files in |
I am hitting an issue where external View components only appear to work when there is an assembly / project reference to the VC from the website project. If you are just dropping an assembly containing the VC (And it's embedded razor view) into some folder, and the website is dynamically discovering the assembly when starting up, then it stops working. I'll put together a repro on github to demonstrate. |
Repro here: https://github.com/dazinator/mvc6-view-components/tree/issue-noprojectreferences I simply forked @johnnyoshika working example, removed the project references and loaded the assemblies (for the external VC, and for the external Controller) dynamically instead. The external controller still works, the VC doesn't.. |
@dazinator: That's interesting. I wonder why your technique doesn't work. |
I've been pulling my hair over this for over a week now and I cannot get it to work. I have a web project
The module project has its views in
But when I try to access I get a
The controller action is hit. Any ideas? I am on *-rc1-final, but had to pull in |
You might want to use the default file provider, in addition to your own. At the moment I think you are overwriting the default file provider. var embeddedFileProviders = componentAssemblies.Select(a => (IFileProvider)new EmbeddedFileProvider(a, a.GetName().Name)).ToList();
services.Configure<RazorViewEngineOptions>(options =>
{
embeddedFileProviders.Add(options.FileProvider);
options.FileProvider = new CompositeFileProvider(embeddedFileProviders);
}); Also you are using areas, did you set up a route? |
@vikekh does your controller need to be marked with an |
I have no idea why it doesn't work haha. I was hoping someone can shed some light on it or perhaps there is something I was missing. I have a feeling its a genuine MVC 6 issue though. The only thing I can think of is that it't an oversight, in that there is a method for registering controllers in stand alone assemblies, but nothing for doing the same for VC's. I.e there is no equivalent to the following, for VC's
If that's correct, I'm assuming there must be some other class you need to implement to provide the candidate assemblies for VC discovery, but I am not sure what that is! |
@dazinator I added the default file provider, however it always seems to be null. @pranavkm Having an area attribute makes the controller method unreachable. There may be other custom implementations in the web project since I'm not the one who set it up. However, I am responsible for upgrading from Beta 7 to RC1. Previously the path |
@vikekh about the null file provider.. that definately shouldn't be the case and i also hit that samr problem when I was using autofac. The solution for me was:
|
@dazinator Already is above! |
@vikekh |
@vikekh You would have to register a |
Any updates on this? There have been some changes to the framework and the solution presented does no longer work. Please provide guidance on adding external views/controllers and view components. |
@Genbox MVC 6 rtm has a new "application parts" API. All you need to do is register your assembly containg your viewcomponent with that Api. If you assembly is allready referenced then you shouldnt need to bother, this is just if you load assemblies at runtime. |
@dazinator I'm loading assemblies at runtime. Could you point me in the direction of this API? A Google search for application parts was unfruitful. |
@Genbox from memory you can do something like:
So in other words the Api is on the builder returned via AddMvc(); |
Edit: Searched a bit for AddApplicationPart() and this article came up: http://www.codeproject.com/Articles/1109475/WebControls/ it also addresses how the view discovery works. |
I think this can be closed now, as it has been established that VC's do work in seperate assemblies.. |
@dazinator I just tried loading view components from another assembly with the newly released VS 2017, and it looks like the views aren't being discovered. I get the standard old error message:
The separate assembly is being referenced. I also tried |
Not taken the plunge and upgraded to vs2017 yet. I'm still in project.json land, and targeting asp.net core 1.0. Some things you might want to check though:
If you can resolve the view using your FileProvider then I'm thinking your issue is probably about getting MVC to use your file provider for view resolution. If you can't then I'm guessing you have an issue with your file provider setup. |
@dazinator I got it to work. Thanks for all of the tips. Things have changed a bit with MS Build. In the external assembly, we need to include this in csproj:
Then in the executing project, we need to reference this NuGet package: Then in Startup, we can do this:
|
Interestingly, changing views in external assemblies doesn't get reflected right away. i.e. reloading the browser doesn't reflect any changes. It requires a full compile for the changes to show up. So it looks like this problem hasn't been fully addressed yet, although it's better than it was before. Before even a compile wasn't enough...you actually had to change and save a C# file. |
Are you using embedded views? I solved this problem by only using Embedded views for production builds. When running under development mode, rather than creating EmbeddedFileProvider's that wrap the assembly , I create PhsyicalFileProviders that point to the actual project source code location as the content root folder. This has the benefit that you can just edit the cshtml view that's in the separate project, and it will update in real time without needing to build. Ofcourse when you deploy your application, you won't be deploying all of the source code for these projects, and that's when you'd fall back to using embedded file providers instead. |
Sadly this is the reality for embedded views. They are baked into the assembly at compile-time, and we won't ever look on disk because we don't know where to look. |
Here's a sample code of @dazinator's technique:
|
I'm trying to get view components to load from a separate assembly. I posted a question on how to do this on StackOverflow and it doesn't seem like anyone knows how to do this. Any chance someone can weigh in on the subject here?
http://stackoverflow.com/q/34236850/188740
Thanks!
The text was updated successfully, but these errors were encountered: