I'd like to define view components (which are new in ASP.NET MVC 6) in a separate assembly from the MVC 6 web startup project so that I can reuse them in multiple web projects. A sample solution might look like this:
BookStore.Components (houses common view components)
BookStore.Web1 (references BookStore.Components)
BookStore.Web2 (references BookStore.Components)
I created a new Class Library (Package) and created a view component inside. I also created the view following the nested folder convention. My BookStore.Components project looks like this:
When I try to invoke this view component from my web project:
#Component.Invoke("BookOfTheMonth")
...I get a 500 error with an empty content body. It seems like the ViewComponent class is discovered, but the razor view for the component isn't.
I also tried to extend DefaultViewComponentDescriptorProvider so that view components from the BookStore.Components assembly can be discovered:
Defined an AssemblyProvider
public class AssemblyProvider : IAssemblyProvider
{
public IEnumerable<Assembly> CandidateAssemblies
{
get
{
yield return typeof(AssemblyProvider).Assembly;
yield return typeof(BookStore.Components.BookOfTheMonthViewComponent).Assembly;
}
}
}
Registered AssemblyProvider using Autofac
builder.RegisterType<AssemblyProvider>()
.AsImplementedInterfaces();
builder.RegisterType<DefaultViewComponentDescriptorProvider>()
.AsImplementedInterfaces();
I'm not sure if the registration of DefaultViewComponentDescriptorProvider above is needed or not, so I tried with and without it, but I still get a 500 error on a page where the view component is invoked.
How can I invoke a view component that lives in a separate assembly from the MVC6 web project?
Update 2017-03-09
Things have changed a bit in Visual Studio 2017 using MS Build. Luckily it's much simpler. Here's how to get this to work:
In the external assembly, add this to the csproj file:
<ItemGroup>
<EmbeddedResource Include="Views/**/*.cshtml" />
</ItemGroup>
In the main web project, add this NuGet package:
Microsoft.Extensions.FileProviders.Embedded
Then in Startup, add the external assembly to the list of File Providers:
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
# Prior to .Net Standard 2.0
# typeof(SampleClassInAssembly).GetTypeInfo().Assembly
));
});
I'll leave the original answer below for now, in case people are still trying to get this to work with older versions of .Net Core and project.json.
================================================================
Here are the steps to make this work.
Make sure your view structure in the components assembly is the same as your web project. Note that there was a mistake in the screenshot that I posted along with my question.
Register CompositeFileProvider in Startup.cs of the web project:
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProvider = new CompositeFileProvider(
new EmbeddedFileProvider(
typeof(BookOfTheMonthViewComponent).GetTypeInfo().Assembly,
"BookStore.Components"
),
options.FileProvider
);
});
Both CompositeFileProvider and EmbeddedFileProvider are new, so you'll need to get these from the aspnetvnext NuGet feed. I did this by adding this source:
Add the dependencies in project.json:
"Microsoft.AspNet.FileProviders.Composite": "1.0.0-*",
"Microsoft.AspNet.FileProviders.Embedded": "1.0.0-*",
Lastly, add this to the project.json of the Components assembly:
"resource": "Views/**"
That should be enough to get this working.
Here is a working demo:
https://github.com/johnnyoshika/mvc6-view-components/tree/master
This answer was formulated from this discussion here: https://github.com/aspnet/Mvc/issues/3750
Update 2016-01-15
There is currently one painful problem with external view components. Any changes you make to the view cshtml file does not automatically get recompiled. Even a forced Visual Studio clean and rebuild doesn't do it. You need to change a .cs file in the components assembly in order to trigger a view recompilation, but it looks like this is something that will be corrected in the future. The reason for this problem is explained here: https://github.com/aspnet/Mvc/issues/3750#issuecomment-171765303
I have done some researching on Github and found that PhysicalFileProvider (link) IFileInfo GetFileInfo(string subpath) method is used by Razor engine (link) for getting real files to compile.
Current implementation of this method
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
return new NotFoundFileInfo(subpath);
}
// Relative paths starting with a leading slash okay
if (subpath.StartsWith("/", StringComparison.Ordinal))
{
subpath = subpath.Substring(1);
}
// Absolute paths not permitted.
if (Path.IsPathRooted(subpath))
{
return new NotFoundFileInfo(subpath);
}
var fullPath = GetFullPath(subpath);
if (fullPath == null)
{
return new NotFoundFileInfo(subpath);
}
var fileInfo = new FileInfo(fullPath);
if (FileSystemInfoHelper.IsHiddenFile(fileInfo))
{
return new NotFoundFileInfo(subpath);
}
return new PhysicalFileInfo(_filesWatcher, fileInfo);
}
private string GetFullPath(string path)
{
var fullPath = Path.GetFullPath(Path.Combine(Root, path));
if (!IsUnderneathRoot(fullPath))
{
return null;
}
return fullPath;
}
We can see here that absolute paths nor permitted and the GetFullPath method combines path with Root which is your main web application root path.
So I assume that u can't open ViewComponent from other folder than the current one.
As of .NetCore v3.x:
[Optional] Remove Microsoft.Extensions.FileProviders.Embedded nuget package
Install Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation nuget package
Call .AddRazorRuntimeCompilation(), e.g: services.AddMvc().AddRazorRuntimeCompilation()
Instead of
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
));
});
Add this:
services.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(
typeof(SampleClassInAssembly).Assembly
));
});
And you are good to go.
Related github issue
Related
How to separate asp.net core mvc project into multiple assembly (.dll)?
I have 3 projects
MyApp Project
Controllers
HomeController.cs
Models
Views
Home
Index.cshtml
HR Project
Controllers
EmployeeController.cs
Models
EMPLOYEE.cs
Views
Employee
Index.cshtml
ACC Project
Controllers
ChartAccountController.cs
Models
ACCOUNT.cs
Views
ChartAccount
Index.cshtml
I want to compile into dll
HR Project
HR.dll
HR.Views.dll
ACC Project
ACC.dll
ACC.Views.dll
I want to add reference those dll (HR.dll, HR.Views.dll, ACC.dll, ACC.Views.dll) into MyApp Project.
And then run MyApp project can access Employee & Chart Account module too.
*** Original Answer (Update below)
If you want to do this you'll need to do the following 2 steps:
You need to load the controllers from the external dlls
There is already a solution for this on stackoverflow: How to use a controller in another assembly in ASP.NET Core MVC 2.0?
It says the following:
Inside the ConfigureServices method of the Startup class you have
to call the following:
services.AddMvc().AddApplicationPart(assembly).AddControllersAsServices();
You need to load the your compiled Razor views, I guess this is what you have in your HR.Views.dll and ACC.Views.dll.
There is also already a solution for this on stackoverflow:
How CompiledRazorAssemblyPart should be used to load Razor Views?
Loading Razor Class Libraries as plugins
This is one possible solution from the links above:
What you need to do is:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.ConfigureApplicationPartManager(ConfigureApplicationParts);
and configure the parts like this
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
var rootPath = HostingEnvironment.ContentRootPath;
var pluginsPath = Path.Combine(rootPath, "Plugins");
var assemblyFiles = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
foreach (var assemblyFile in assemblyFiles)
{
try
{
var assembly = Assembly.LoadFile(assemblyFile);
if (assemblyFile.EndsWith(".Views.dll"))
apm.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
else
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
catch (Exception e) { }
}
}
If you also have javascript and css files in our separate MVC projects, you will need to embed this to your dll otherwise your main app wont see it. So in your HR and ACC project, you'll need to add this in your .csproj file:
<ItemGroup>
<EmbeddedResource Include="wwwroot\**" />
</ItemGroup>
And just to be clear, I agree with the other comments, I don't think it is a good architecture, but it is possible to do it if you want.
*** Updated (Worked)
Just a step:
Edit Startup.cs in ConfigureServices method
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApplicationPartManager(ConfigureApplicationParts);
and method:
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
var rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblyFiles = Directory.GetFiles(rootPath , "*.dll");
foreach (var assemblyFile in assemblyFiles)
{
try
{
var assembly = Assembly.LoadFile(assemblyFile);
if (assemblyFile.EndsWith(this.GetType().Namespace + ".Views.dll") || assemblyFile.EndsWith(this.GetType().Namespace + ".dll"))
continue;
else if (assemblyFile.EndsWith(".Views.dll"))
apm.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
else
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
catch (Exception e) { }
}
}
What you're wanting is not possible, or perhaps better said: it's not a good idea, for sure.
In general, you need to use Razor Class Libraries. All common functionality would then go into those RCLs. Your entire HR and ACC projects could be RCLs, unless you need to run those independently, as full web apps, as well. In which case, you'd need a structure more like:
HR RCL
HR Web App (depends on HR RCL)
ACC RCL
ACC Web App (depends on ACC RCL)
Main App (depends on HR RCL and ACC RCL)
In either case, you'd put all the controllers, views, and static resources that need to be shared in the RCL, so if you do have actual HR/ACC web apps, those would be pretty light: mostly just consisting of Program and Startup and a dependency on their respective RCLs.
For more information, see the documentation on Razor Class Libraries.
I am trying to add a plugin like functionality to my application and having hard time making the precompiled views to be found.
So lets say i have a Razor Class Library that compiled to plugin.dll and plugin.views.dll
I am successfully load and add plugin.dll
Assembly PLUGIN_ASSEMBLY = null;
try
{
PLUGIN_ASSEMBLY = Assembly.LoadFile(PLUGIN.PluginFileName);
Assembly.LoadFile(PLUGIN.PluginViewsFileName);
}
catch (FileLoadException)
{
throw;
}
Then the assembly is added with
MVC_BUILDER.AddApplicationPart(PLUGIN_ASSEMBLY);
Then i add the plugin base path so its normal views would be discovered
MVC_BUILDER.AddRazorOptions(o =>
{
IFileProvider physicalProvider = new PhysicalFileProvider(PLUGIN.BasePath);
IFileProvider compositeProvider = new CompositeFileProvider(physicalProvider);
o.FileProviders.Add(compositeProvider);
});
All above works fine except that i can only use the physically located views and not the ones from plugin.views.dll
What would be correct approach to add the views.dll and make the views discovered?
I spent all day to make it work.. and it worked.
In web app razor knew where from take precompiled views, but in console app it doesn't (maybe it my fault). Let's help him :)
At first, we need name of assembly with views:
var viewAssembly = Assembly.Load(PLUGIN_ASSEMBLY.GetName().Name + ".Views");
Second, we should create provider, that will extract all compiled views from assembly:
var viewAssemblyPart = new CompiledRazorAssemblyPart(viewAssembly);
And last, but not least - add it to collection of other providers:
MVC_BUILDER.PartManager.ApplicationParts.Add(viewAssemblyPart);
Enjoy!
Special thanks to sources from github :)
Maybe duplicate of this already, but since that post does not have any answer, I am posting this question.
The new Razor Class Library is awesome, but it cannot pack libraries files (like jQuery, shared CSS).
Can I somehow reuse the CSS across multiple Razor Page projects, either using Razor Class Library or anything else (my purpose is that, multiple websites use the same CSS, and a single change applies to all projects).
I have tried creating the folder wwwroot in the Razor Class Library project, but it does not work as expected (I can understand why it should not work).
Ehsan answer was correct at the time of asking (for .NET Core 2.2), for .NET Core 3.0 onwards (including .NET 7 when I update this link), RCL can include static assets without much effort:
To include companion assets as part of an RCL, create a wwwroot folder in the class library and include any required files in that folder.
When packing an RCL, all companion assets in the wwwroot folder are automatically included in the package.
The files included in the wwwroot folder of the RCL are exposed to the consuming app under the prefix _content/{LIBRARY NAME}/. For example, a library named Razor.Class.Lib results in a path to static content at _content/Razor.Class.Lib/.
You need to embed your static assets into your Razor Class Library assembly. I think the best way to get how to do it is to take a look at ASP.NET Identity UI source codes.
You should take the following 4 steps to embed your assets and serve them.
Edit the csproj file of your Razor Class Library and add the following lines.
<PropertyGroup>
....
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
....
</PropertyGroup>
<ItemGroup>
....
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
.....
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="wwwroot\**\*" />
<Content Update="**\*.cshtml" Pack="false" />
</ItemGroup>
In your Razor Class Library, create the following class to serve and route the assets. (it assumes your assets are located at wwwroot folder)
public class UIConfigureOptions : IPostConfigureOptions<StaticFileOptions>
{
public UIConfigureOptions(IHostingEnvironment environment)
{
Environment = environment;
}
public IHostingEnvironment Environment { get; }
public void PostConfigure(string name, StaticFileOptions options)
{
name = name ?? throw new ArgumentNullException(nameof(name));
options = options ?? throw new ArgumentNullException(nameof(options));
// Basic initialization in case the options weren't initialized by any other component
options.ContentTypeProvider = options.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
if (options.FileProvider == null && Environment.WebRootFileProvider == null)
{
throw new InvalidOperationException("Missing FileProvider.");
}
options.FileProvider = options.FileProvider ?? Environment.WebRootFileProvider;
var basePath = "wwwroot";
var filesProvider = new ManifestEmbeddedFileProvider(GetType().Assembly, basePath);
options.FileProvider = new CompositeFileProvider(options.FileProvider, filesProvider);
}
}
Make the dependent web application to use your Razor Class Library router. In the ConfigureServices method of Startup Class, add the following line.
services.ConfigureOptions(typeof(UIConfigureOptions));
So, now you can add a reference to your file. ( let's assume it's located at wwwroot/js/app.bundle.js).
<script src="~/js/app.bundle.js" asp-append-version="true"></script>
In .NET Core 3.1, RCL includes assets inside wwwroot folder to consuming app under _content/{LIBRARY NAME}.
We can change _content/{LIBRARY NAME} path to different path name by editing RCL project propeties and placing StaticWebAssetBasePath.
PropertyGroup>
<StaticWebAssetBasePath Condition="$(StaticWebAssetBasePath) == ''">/path</StaticWebAssetBasePath>
</PropertyGroup>
Now you can access files with /path/test.js.
Thanks for the helpful information Ehsan.
Here is an expanded version to allow for debugging javascript and typescript as well has being able to make changes without recompile. TypeScript debugging isn't working in Chrome but is in IE. If you happen to know why please post a response. Thanks!
public class ContentConfigureOptions : IPostConfigureOptions<StaticFileOptions>
{
private readonly IHostingEnvironment _environment;
public ContentConfigureOptions(IHostingEnvironment environment)
{
_environment = environment;
}
public void PostConfigure(string name, StaticFileOptions options)
{
// Basic initialization in case the options weren't initialized by any other component
options.ContentTypeProvider = options.ContentTypeProvider ?? new FileExtensionContentTypeProvider();
if (options.FileProvider == null && _environment.WebRootFileProvider == null)
{
throw new InvalidOperationException("Missing FileProvider.");
}
options.FileProvider = options.FileProvider ?? _environment.WebRootFileProvider;
if (_environment.IsDevelopment())
{
// Looks at the physical files on the disk so it can pick up changes to files under wwwroot while the application is running is Visual Studio.
// The last PhysicalFileProvider enalbles TypeScript debugging but only wants to work with IE. I'm currently unsure how to get TS breakpoints to hit with Chrome.
options.FileProvider = new CompositeFileProvider(options.FileProvider,
new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, $"..\\{GetType().Assembly.GetName().Name}\\wwwroot")),
new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, $"..\\{GetType().Assembly.GetName().Name}")));
}
else
{
// When deploying use the files that are embedded in the assembly.
options.FileProvider = new CompositeFileProvider(options.FileProvider,
new ManifestEmbeddedFileProvider(GetType().Assembly, "wwwroot"));
}
_environment.WebRootFileProvider = options.FileProvider; // required to make asp-append-version work as it uses the WebRootFileProvider. https://github.com/aspnet/Mvc/issues/7459
}
}
public class ViewConfigureOptions : IPostConfigureOptions<RazorViewEngineOptions>
{
private readonly IHostingEnvironment _environment;
public ViewConfigureOptions(IHostingEnvironment environment)
{
_environment = environment;
}
public void PostConfigure(string name, RazorViewEngineOptions options)
{
if (_environment.IsDevelopment())
{
// Looks for the physical file on the disk so it can pick up any view changes.
options.FileProviders.Add(new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, $"..\\{GetType().Assembly.GetName().Name}")));
}
}
}
Please take note that this solutions provided will only work for server side applications. If you are using Blazor client side it will not work. To include static assets on Blazor client side from a razor class library you need to reference directly the assets like this:
<script src="_content/MyLibNamespace/js/mylib.js"></script>
I wasted hours trying to figure out this. Hope this helps someone.
There's a simpler solution: in your RCL's project, you can flag the wwwroot to be copied to the publish directory:
<ItemGroup>
<Content Include="wwwroot\**\*.*" CopyToPublishDirectory="Always" />
</ItemGroup>
When you deploy an app that depends on the RCL, all the files are accessible as expected. You just need to be careful that there's no naming conflict.
Caveat: this only works when deploying on Azure, but not on your local machine (you'll need a provider for that).
Addition to the answer of #revobtz
The name of my project file is different from my namespace, so I found out that is should be the name of the project file instead of the namespace:
<script src="_content/<Name of project file>/js/mylib.js"></script>
Following on from this question, I have now set up pre-compiled views in my asp.net core application which is compiling to a DLL from the command line using the
dotnet razor-precompile
command. I have then packaged it as a nuget package using
dotnet pack
and added the package as a reference to the project I’ve removed the views from.
I have then created a new class which implements IViewLocationExpander and set this up in the setup.cs method of my project and I can see it searching my new location for the views. However, I don’t know what to put as the search path for a pre-compiled view, as there are no .cshtml files in there. I simply get an InvalidOperationException with the view not found.
Has anyone done this before or able to suggest how I may add these precompiled views to the search path?
Thanks
I was amazed, it directly worked this way:
I just registered into my main project a custom ViewExpander:
services.AddMvc().AddRazorOptions(options =>
{
options.ViewLocationExpanders.Clear();
options.ViewLocationExpanders.Add(new TestViewLocationExpander());
};
The expander itself:
public class TestViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (viewLocations == null)
{
throw new ArgumentNullException(nameof(viewLocations));
}
yield return "~/Test/Test.cshtml";
}
}
Then I have referenced the *.PrecompiledViews.dll of my other project, which contains a Test/Test.cshtml.
And voila, every page in my main application was showing this one.
I have a simple question about dependecy registration.
I'm developing a brand new web application that use Engine Context paradigm with Autofac container. For any library on the solution I have one class implementing IDependencyRegistrar that implement a common Register method, due to add one the container some specific implementation of some interfaces and components.
In this way, a base Core library (running at application startup) provide a RegisterDependencies method that lookup on every Executing Assembly to discover all the DDL's used by the application and registering them on Autofac Container.
The code that provide this behavior is:
builder = new ContainerBuilder();
var drTypes = typeFinder.FindClassesOfType<IDependencyRegistrar>();
var drInstances = new List<IDependencyRegistrar>();
foreach (var drType in drTypes)
drInstances.Add((IDependencyRegistrar) Activator.CreateInstance(drType));
//sort
drInstances = drInstances.AsQueryable().OrderBy(t => t.Order).ToList();
foreach (var dependencyRegistrar in drInstances)
dependencyRegistrar.Register(builder, typeFinder, config);
builder.Update(container);
Where the FindClassOfType<IDependencyRegistrar> works thanks to a Method implementation like that:
public virtual IList<Assembly> GetAssemblies()
{
var addedAssemblyNames = new List<string>();
var assemblies = new List<Assembly>();
if (LoadAppDomainAssemblies)
AddAssembliesInAppDomain(addedAssemblyNames, assemblies);
AddConfiguredAssemblies(addedAssemblyNames, assemblies);
return assemblies;
}
And, AddAssemblyInAppDomain is:
private void AddAssembliesInAppDomain(List<string> addedAssemblyNames, List<Assembly> assemblies)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (Matches(assembly.FullName))
{
if (!addedAssemblyNames.Contains(assembly.FullName))
{
assemblies.Add(assembly);
addedAssemblyNames.Add(assembly.FullName);
}
}
}
}
The problem is: when I end up on adding in mysolution the MVC project (the front-end), I've referenced on it only direct accessing library (service layer and some infrastructure components) but no DataLayer components and some other DLL. Due to the fact that MVC not referencing directly some libraries of deep layers, my Engine Context doesn't see the others sub-components and not registering them on the Autofac container, causing a
'no registered services'
exception when execution make explicit request on them.
The whole system just works if I add reference to any library from the MVC project but, for layered architectured application, this is not a best practice: my MVC need to know nothing about DataLayer or others low-layered services.
However, in this way, no ExecutingAssembly are discovered, so, not dependency are registered anymore.
Wich is the best approch to resolve this situation without referencing all assemblies directly from main MVC project?
What you are trying to do is described in Autofac documentation as Assembly Scanning, take a look here. Basically, to get all assemblies in IIS-hosted application you need this piece of code:
var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
EDIT:
Ok, so I understand the situation is like this:
Project Web is a MVC web app.
Project Model is a class library where you have your contracts (interfaces) defined, e.g. for DAL, but also for Web.
Project DAL contains some implementations of contracts from Model.
There might be some additional class libraries, but they all uses Model for contracts.
So to sum up - all projects have reference to Model, but they have no references to each other.
I think for every library (except Model) you should create a module. To do so, create a class implementing Module type from Autofac library and override Load method - put all your module registration in there. Then, in Web app start you should load all assemblies and register their modules. But, as you mentioned, assemblies other than Web are not present in bin directory; you should copy them there "manually", for example in Post-Build action (Project Properties -> Build Events -> Post-Build action). The following command should do the work:
xcopy /Y "$(TargetDir)*.dll" "$(ProjectDir)..\{Your Web App}\bin"
Also, in your solution properties you should set, that Web project "depends" on all other projects. It would assure all other libraries would be build before Web. It does not add any reference between these assemblies.
Then, during application startup, you should search for you assemblies in bin folder and register each assembly module, like this:
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterControllers(typeof(MvcApplication).Assembly);
var libFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/bin"));
var libFiles = libFolder.GetFiles("*.dll", SearchOption.AllDirectories);
foreach (var lib in libFiles)
{
var asm = Assembly.LoadFrom(lib.FullName);
containerBuilder.RegisterAssemblyModules(asm);
}
var container = containerBuilder.Build();
You might want to add some filter to libFolder.GetFiles() to retreive only your assemblies, not all from bin.
If your other assemblies contains Mvc Controllers, you should take a look how to manage the situation here (see Initializer class). Basically, in pre-start of application you would need to add assemblies to BuildManager. Otherwise, the code above should work just fine.
If you are working on a non-web project then my answer might help?
To your Ioc class add a method i.e:
public static void SetIocForTesting(bool forUnitTesting)
{
_testContext = forUnitTesting;
}
Sample container set-up code, delegate out the responsibility of getting the assemblies to load into the builder. i.e GetModules():
public static IContainer Container
{
get
{
if (_container != null)
{
return _container;
}
var builder = new ContainerBuilder();
foreach (var lib in GetModules())
{
builder.RegisterAssemblyModules(lib);
}
_container = builder.Build();
return _container;
}
}
When scanning for Assemblies, switch on the testContext variable:
private static IEnumerable<Assembly> GetModules()
{
if (_testContext)
{
return AppDomain.CurrentDomain.GetAssemblies();
}
var currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (currentPath == null)
{
throw new NullReferenceException("Unable to build the container because currentPath variable is null.");
}
// XXXX = assign a wild card
var libFolder = new DirectoryInfo(currentPath);
var libFiles = libFolder.GetFiles("XXXX.*.dll", SearchOption.TopDirectoryOnly);
return libFiles.Select(lib => Assembly.LoadFrom(lib.FullName)).ToList();
}
When unit testing your IoC provider and a registration:
protected virtual void GivenThat()
{
IocProvider.SetIocForTesting(true);
}
.. you have a method that switches the IoC to ensure it works correctly with all assemblies referenced and loaded by your test project. The above method lives inside an abstract base class I use for BDD style unit testing.
Your test project usually ends up referencing a lot of assemblies which means resolving services have a higher success rate.
Finally, for non UnitTesting code add a static constructor:
static IocProvider()
{
_testContext = false;
}
This will ensure a default work flow for production code.
Feel free to play with the above format to suit your needs; I hope it helps someone in the way the above question and answer helped me.