Hit a bit of a stumbling block when trying to test a Nancy module from a test project. My test code looks pretty standard:
[TestMethod]
public void Should_return_status_ok_when_route_exists()
{
// Given
var bootstrapper = new DefaultNancyBootstrapper();
var browser = new Browser(bootstrapper);
// When
var result = browser.Get("/", with =>
{
with.HttpRequest();
});
// Then
Assert.AreEqual(result.StatusCode, HttpStatusCode.OK);
}
I get an unable to locate view exception when my module tries to render the view. If I run the project normally the module finds the view. It's only when invoked from the test project that the module can't find it.
The problem is that the views aren't anywhere close your test project, and since the IRootPathProvider is pointing at the wrong place, it can't find them. Two ways to get around this is use the ConfigurableBootstrapper (which is more or less the same as the Default one, but the the possibility to override stuff when initialized) and tell it to use your custom root path provider
var bootstrapper = new ConfigurableBootstrapper(with => {
with.RootPathProvider<CustomRootPathProvider>();
});
You would then implement public class CustomRootPathProvider : IRootPathProvider and point it in the right place.
The second solution would be to set your views to always copy to the output directory, I believe that should also solve it
Related
I have a very simple question.
Is it possible to run Nunit tests inside an asp.net web app?
Here is an example project:
MyApp -> ASP.Net app (.net5)
MyTests -> Nunit Tests (.net5)
My Asp.net project (MYApp) contains all my controllers and such, with a depency on NUnit.Engine and my test project.
There is another Test project (MyTests), which is just a dummy project.
I want to be able to run in a controller, inside my web app, my tests.
Example controller:
namespace MyApp.Controllers
{
[Route("api/tests")]
[ApiController]
public class TestController: ControllerBase
{
// Some helper class to verify everything is working somehow
private class ReportListener : ITestEventListener
{
public void OnTestEvent(string report)
{
Console.WriteLine(report);
}
}
[HttpGet]
public async Task<ActionResult> Trigger()
{
try
{
using ITestEngine engine = TestEngineActivator.CreateInstance();
engine.Initialize();
engine.WorkDirectory = Path.Combine(Directory.GetCurrentDirectory(), "../","MyTests/");
// Create a simple test package - one assembly, no special settings
TestPackage package = new TestPackage(#".\bin\Debug\net5.0\MyTests.dll"); //Just for debugging and testing
// Get a runner for the test package
using ITestRunner runner = engine.GetRunner(package);
runner.Load();
// Run all the tests in the assembly
XmlNode testResult = runner.Run(listener: new ReportListener(), TestFilter.Empty);
var outputs = Enumerable.Empty<string>();
foreach (XmlNode elem in testResult.SelectNodes("//test-case/output"))
{
outputs = outputs.Append(elem.InnerText);
}
}catch(Exception e)
{
}
return Ok();
}
}
}
But unfortunately all my attemps so far have failed.
Am I missing something?
Is Nunit.Engine not made to be run in an asp.net context?
I am building all this in .NET5.0 (company policy)
If needed I can provide an example project
There could be more, but one small thing would explain the failure...
The NUnit engine defaults to running tests in a separate process, which it launches. So, assuming that your code is working correctly as written, a ProcessRunner will be created and the engine will communicate with it, telling it to run your tests.
This could fail in one of two ways:
You may not have permission to create a process.
If you succeed in creating it, the code will definitely not be running in the asp.net context. In that case, it would probably error out and terminate with very little debug information provided.
A simple fix is to add a setting to the test package, telling the engine to run the tests in process.
TestPackage package = new TestPackage(#".\bin\Debug\net5.0\MyTests.dll");
package.AddSetting("ProcessModel", "InProcess");
If you get a second error after doing this, it should at least result in a clearer message and you should be able to debug through the code.
I'm having trouble using DryIoc for constructor injection into a ViewModel using Prism with Xamarin. I am using the Nuget package Prism.DryIoc.Forms.
In my project I get the following error in AuthenticatePage.xaml.g.cs
Unable to resolve Object {RequiredServiceType=Project.ViewModels.AuthenticatePageViewModel} with 1 arg(s)
in wrapper Func<Xamarin.Forms.Page, Object> {RequiredServiceType=Project.ViewModels.AuthenticatePageViewModel} with 1 arg(s)
from container
with normal and dynamic registrations:
MainPage, {ID=44, ImplType=Project.Views.MainPage}}
NavigationPage, {ID=43, ImplType=Xamarin.Forms.NavigationPage}}
AuthenticatePage, {ID=45, ImplType=Project.Views.AuthenticatePage}}
Specifically, it points to the line
private void InitializeComponent() {
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(AuthenticatePage));
}
Of note is that if I call the following in App.OnInitialized, the object resolves fine:
c.Register<INegotiator, Negotiator>(Reuse.Singleton);
var n = c.Resolve<INegotiator>();
n.ResumeSessionAsync(); // This works fine, no problems.
await NavigationService.NavigateAsync("NavigationPage/AuthenticatePage"); // Error thrown here
If I remove the constructor injection from my ViewModel it works fine (Aside from keeping the default navigationService injection, which works fine). Even trying to inject a basic class like ILogger (no dependencies) fails.
public AuthenticatePageViewModel(INavigationService navigationService, ILogger logger) : base (navigationService)
{
Title = "Authentication Page...";
}
I'm going to keep investigating, but is it obvious to someone here if I'm fundamentally doing something wrong? If I had to guess I would say it's to do with a conflict with Prisms built in Ioc container and DryIoc?
Edit:
I'm using the latest version of Prism.DryIoc.Forms available on NuGet (7.0.0.396) which says it includes DryIoc 2.12.26. I have so far simply followed the template available for Visual Studio which lists setting up navigation as follows:
protected override async void OnInitialized()
{
InitializeComponent();
var c = new Container();
c.Register<ILogger, LoggerConsole>(Reuse.Singleton);
c.RegisterMany(new[] { Assembly.Load("Project.UWP") },
serviceTypeCondition: type => type == typeof (ILocalFileHandler));
c.Register<INegotiator, Negotiator>(Reuse.Singleton);
// var n = c.Resolve<INegotiator>();
// n.ResumeSessionAsync(); // <- This will run fine. Negotiator class has ILogger and ILocalFileHandler injected into it.
await NavigationService.NavigateAsync("NavigationPage/AuthenticatePage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage>();
containerRegistry.RegisterForNavigation<AuthenticatePage>();
}
I can't find any info online on if/how I should be using Prism.DryIoc.DryIocContainerExtensions to set up navigation? Even modifying the sample app to include basic construction injection results in the error "Value Cannot Be Null" in the same xaml.g.cs file?
Prism 7.0 and below allows the exception to bubble up, in order to diagnose the root cause of your issue you want to better diagnose this issue I suggest you do a little try/catch to see what and where the error really is.
protected override void OnInitialized()
{
try
{
// Check if there is an initialization exception
var page = new AuthenticationPage();
// Validate that the page resolves ok
var page2 = Container.Resolve<object>("AuthenticationPage");
// Validate that your ILogger interface is registered and resolves ok
var logger = Container.Resolve<ILogger>();
// Check for Registration/initialization exceptions
var vm = Container.Resolve<AuthenticationPageViewModel>();
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
System.Diagnostics.Debugger.Break();
}
}
You haven't specified at what point you're getting this error, though typically with XAML Compilation enabled you would see exceptions in the {pageName}.xaml.g.cs during compilation and not runtime. Either way, given that your exception is coming from the generated XAML code behind class, this tells me it is most likely a problem with your XAML. A very simple way to validate this is to remove all of the XAML content in your AuthenticationPage so that you have an empty page.
Given the code you've provided as part of your question, I would say you have no registration for your ILogger interface which would likely throw an exception causing the problem you're seeing. Regardless of what/where the error is, the try/catch shown above would be the easiest way to determine the root cause.
Following #Dan S.'s diagnoses suggestion as well as reading this article (http://brianlagunas.com/whats-new-in-prism-for-xamarin-forms-7-0/) I realized that I should have been using the Prism.Ioc.ContainerRegistry abstraction layer to interface with DryIoc. Prior to this I had been working directly with DryIoc's classes.
Once I modified my registration code to use Prism.Ioc.IContainerRegistry it worked perfectly.
protected override void RegisterTypes(IContainerRegistry cr)
{
cr.Register<ILogger, LoggerConsole>();
cr.GetContainer().RegisterMany(new[] { Assembly.Load("Project.UWP") },
serviceTypeCondition: type => type == typeof(ILocalFileHandler));
cr.Register<INegotiator, Negotiator>();
cr.RegisterForNavigation<NavigationPage>();
cr.RegisterForNavigation<MainPage>();
cr.RegisterForNavigation<AuthenticatePage>();
}
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.
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
I have an application which contains multiple hubs all on unique paths, so when calling the default :
routes.MapHubs("path", new HubConfiguration(...));
It blows up saying that the signalr.hubs is already defined (as mentioned here MapHubs not needed in SignalR 1.01?).
Now I can understand that it should only be called once, but then you will only get 1 path, so is there any way to handle a path per hub scenario? like how with MVC you specify the controller and action? so something like:
routes.MapHub<SomeHub>("path", new HubConfiguration(...));
== Edit for more info ==
It is mentioned often that you should never need to call this map hubs more than once, and in most scenarios I can agree, however I would not say that this is going to be the case for all applications.
In this scenario it is a website which at runtime loads any plugins which are available, each plugin is exposed the dependency injection framework to include its dependencies and the route table to include its routes. The hubs may have nothing to do with each other (other than the fact that they are both hub objects). So the hubs are not all known up front and are only known after the plugins are loaded, and yes I could wait until after this and try binding the hubs there, however then how do I have custom routes for each one then?
This seems to be a case of SignalR trying to abstract a little too much, as I dont see it being a bad idea to have custom routes rather than the default "/signalr", and as the routes all have different responsibilities it seems bad to have one entry route for them all.
So anyway I think the question still stands, as I dont see this as being a bad use case or bad design it just seems to be that I want to be able to have a route with a hub applied to it, much like in mvc you apply a controller and action to a route.
You shouldn't need more than the signalr.hubs route. If you point your browser to that route, you will see it automatically finds all public types assignable to IHub and creates a JavaScript proxy for them. You can interact with different hubs by name from JavaScript, i.e. if you have the following Hub:
public class GameHub : Hub
You can connect to that specific hub by doing:
var gameHubProxy = $.connection.gameHub;
You can also explicitly specify a name for your hub by adding the HubNameAttribute to the class:
[HubName("AwesomeHub")]
public class GameHub : Hub
You can then retrieve the specific proxy by doing
var awesomeHubProxy = $.connection.awesomeHub;
UPDATE:
I'm not sure whether SignalR will be able to run on multiple paths in the same application. It could potentially mess things up and the default assembly locator won't be able to pick up hubs loaded at runtime anyway.
However, there is a solution where you can implement your own IAssemblyLocator that will pick up hubs from your plugin assemblies:
public class PluginAssemblyLocator : DefaultAssemblyLocator
{
private readonly IEnumerable<Assembly> _pluginAssemblies;
public PluginAssemblyLocator(IEnumerable<Assembly> pluginAssemblies)
{
_pluginAssemblies = pluginAssemblies;
}
public override IList<Assembly> GetAssemblies()
{
return base.GetAssemblies().Union(_pluginAssemblies).ToList();
}
}
After you've loaded your plugins, you should call MapHubs and register an override of SignalRs IAssemblyLocator service:
protected void Application_Start(object sender, EventArgs e)
{
// Load plugins and let them specify their own routes (but not for hubs).
var pluginAssemblies = LoadPlugins(RouteTable.Routes);
RouteTable.Routes.MapHubs();
GlobalHost.DependencyResolver.Register(typeof(IAssemblyLocator), () => new PluginAssemblyLocator(pluginAssemblies));
}
NOTE: Register the IAssemblyLocator AFTER you've called MapHubs because it will also override it.
Now, there are issues with this approach. If you're using the static JavaScript proxy, it won't be re-generated every time it's accessed. This means that if your /signalr/hubs proxy is accessed before all plugins/hubs has been loaded, they won't be picked up. You can get around this by either making sure that all hubs are loaded by the time you map the route or by not using the static proxy at all.
This solution still requires you to get a reference to your plugin assemblies, I hope that's feasible...