Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.Net 5 MVC 6 Startup.cs Assembly Decoupling in Beta8

I am working on an eCommerce site using Asp.Net 5 and MVC6 following Onion Architecture (OA) so that we have loose coupling between the layers. I also want to decouple Startup code in its own assembly rather than having it in the MVC project.

In beta7 It was very easy to move the Startup.cs to a class library (Bootstrapper) as explained here. One interesting fact using the mentioned approach is that I didn't have to reference the Bootstrapper assembly from the MVC project. At runtime, hosting under IISExpress, through assembly scanning it was able to find the Bootstrapper assembly mentioned in the Microsoft.AspNet.Hosting.ini file. This was possible by specifying the location in the global.json

{
  "projects": [ "Source/Projects","Source/Bootstrapper" ],
  "sdk": {
        "architecture": "x64",
        "runtime": "clr",
        "version": "1.0.0-beta7"
    }
}

The Bootstrapper project will have reference to all other projects like Infrastructure, Services etc in order to hook up Dependency Injection.

The reason for not referencing the Bootstrapper project in MVC project, following Onion Architecture rules, is to avoid having access to Infrastructure code directly from MVC project. So this was all working fine until I upgraded to Beta8 this morning.

As the hosting model is changed from IIS to Kestrel, I had to refactor the global.json and project.json files as below

global.json

{
  "projects": [ "Source/Projects","Source/Bootstrapper" ],
  "sdk": {
        "architecture": "x64",
        "runtime": "clr",
        "version": "1.0.0-beta8"
    }
}

project.json

{ 
  "dependencies": {
    "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8",
    "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8",
    "....",
    "....",
},

 "commands": {
    "web": "Microsoft.AspNet.Server.Kestrel"   
    } 
}

After making the above changes, I started getting the following error regardless whether I run it using dnx command or directly via Visual Studio

Internal Server Error System.InvalidOperationException A type named 'StartupDevelopment' or 'Startup' could not be found in assembly 'EcommerceMvcApp'. at Microsoft.AspNet.Hosting.Startup.StartupLoader.FindStartupType(String startupAssemblyName, IList diagnosticMessages) at Microsoft.AspNet.Hosting.Internal.HostingEngine.EnsureStartup() at Microsoft.AspNet.Hosting.Internal.HostingEngine.EnsureApplicationServices() at Microsoft.AspNet.Hosting.Internal.HostingEngine.BuildApplication()

Turned out that I have to specify the config file or inline arguments to the web command as explained here. After following the suggestion, I tried running the application and this time I started getting the error below

System.IO.FileNotFoundException Could not load file or assembly 'Bootstrapper' or one of its dependencies. The system cannot find the file specified. at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.Assembly.Load(AssemblyName assemblyRef) at Microsoft.AspNet.Hosting.Startup.StartupLoader.FindStartupType(String startupAssemblyName, IList diagnosticMessages) at Microsoft.AspNet.Hosting.Internal.HostingEngine.EnsureStartup() at Microsoft.AspNet.Hosting.Internal.HostingEngine.EnsureApplicationServices() at Microsoft.AspNet.Hosting.Internal.HostingEngine.BuildApplication()

The solution requires that I add a reference to the Bootstrapper project in the MVC project and it works. However, it defeats the purpose of having a separate Bootstrapper assembly in the first place.

The question is, why it is unable to find the Bootstrapper assembly like it used to do in Beta7, using the sources specified under "projects" in global.json or is the new hosting model ignoring the global.json? Is there a way to specify the location of the Startup assembly?

Update 1

Just want to highlight that in Beta7 it also works using "dnx command" for both Microsoft.AspNet.Server.WebListener and Microsoft.AspNet.Server.Kestrel.

"commands": {
        "kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5004 --config wwwroot/Microsoft.AspNet.Hosting.ini",
        "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5004 --config wwwroot/Microsoft.AspNet.Hosting.ini"
    }

However, the dnx command (using Microsoft.AspNet.Hosting.json file) fails for both servers in Beta8. If someone is wondering that it's something to do with IIS Helios component in Beta7, it's not the case. I am baffled as why the assembly lookup stopped working in Beta8

Update 2

Here is the stack trace that I get when I try to run in Beta8 using IISExpress. Looks like it's trying to find the assembly in the dnx bin folder.

System.IO.FileNotFoundException: Could not load file or assembly 'Bootstrapper' or one of its dependencies. The system cannot find the file specified. File name: 'Bootstrapper' at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.Assembly.Load(AssemblyName assemblyRef) at Microsoft.AspNet.Hosting.Startup.StartupLoader.FindStartupType(String startupAssemblyName, IList`1 diagnosticMessages) at Microsoft.AspNet.Hosting.Internal.HostingEngine.EnsureStartup() at Microsoft.AspNet.Hosting.Internal.HostingEngine.EnsureApplicationServices() at Microsoft.AspNet.Hosting.Internal.HostingEngine.BuildApplication()

=== Pre-bind state information === LOG: DisplayName = Bootstrapper (Partial) WRN: Partial binding information was supplied for an assembly: WRN: Assembly Name: Bootstrapper | Domain ID: 1 WRN: A partial bind occurs when only part of the assembly display name is provided. WRN: This might result in the binder loading an incorrect assembly. WRN: It is recommended to provide a fully specified textual identity for the assembly, WRN: that consists of the simple name, version, culture, and public key token. WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue. LOG: Appbase = file:///C:/Users/sshassan/.dnx/runtimes/dnx-clr-win-x86.1.0.0-beta8/bin/ LOG: Initial PrivatePath = NULL Calling assembly : (Unknown). === LOG: This bind starts in default load context. LOG: No application configuration file found. LOG: Using host configuration file: LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config. LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind). LOG: Attempting download of new URL file:///C:/Users/sshassan/.dnx/runtimes/dnx-clr-win-x86.1.0.0-beta8/bin/Bootstrapper.DLL. LOG: Attempting download of new URL file:///C:/Users/sshassan/.dnx/runtimes/dnx-clr-win-x86.1.0.0-beta8/bin/Bootstrapper/Bootstrapper.DLL. LOG: Attempting download of new URL file:///C:/Users/sshassan/.dnx/runtimes/dnx-clr-win-x86.1.0.0-beta8/bin/Bootstrapper.EXE. LOG: Attempting download of new URL file:///C:/Users/sshassan/.dnx/runtimes/dnx-clr-win-x86.1.0.0-beta8/bin/Bootstrapper/Bootstrapper.EXE.

Perhaps, if I run dnu publish and host it under IIS it will work, but that means that I would have to publish it every time I make the change

like image 247
Shahzad Hassan Avatar asked Oct 20 '15 15:10

Shahzad Hassan


1 Answers

I was stuck in a similar problem. It looks like we do not want to make references from UI layer to Infraestructure layer (we are very stricts), not even for making the dependency resolution.

Maybe it is possible by using late binding (I just heard talked about it), but I think that you should read this article. It basically says that a Composition Root isn't reusable, and there should be one per application (i.e., one for UI.Web, another for UI.Console, and so on).

That responds also my question about what having the DI resolution in UI.Web, but need another UI, let's say Console (answer: it's preferable to make anoter DI resolution in Console, and it'll have it's own resolution dependences related to how a Console Application actually works).

I hope having give you a good point to clarify this issue.

like image 102
Franco Avatar answered Oct 14 '22 18:10

Franco