I'm using the automatic build versioning mentioned in this question (not the selected answer but the answer that uses the [assembly: AssemblyVersion("1.0.*")] technique). I'm doing this in the footer of my Site.Master file in MVC 2. My code for doing this is as follows:
<div id="footer">
webmaster#foo.com - Copyright © 2005-<%= DateTime.Today.Year.ToString() %>, foo LLC. All Rights Reserved.
- Version: <%= Assembly.GetEntryAssembly().GetName().Version.ToString() %>
</div>
The exception I get is a Object reference not set to an instance of an object because GetEntryAssembly() returns NULL. My other options don't work either. GetCallingAssembly() always returns "4.0.0.0" and GetExecutingAssembly() always returns "0.0.0.0". When I go look at my DLLs, everything is versioning as I would expect. But I cannot figure out how to access it to display in my footer!!
That's because Assembly.GetEntryAssembly() is returning null: there is no "entry" assembly in an ASP.NET site (because the .NET framework is hosted in the w3wp.exe process). Assembly.GetEntryAssembly() is used to get the .exe assembly that you launched from (usually in a console or Windows application)
The reason Assembly.GetAssembly(this.GetType()) is returning an assembly with version "0.0.0.0" is because ASP.NET compiles your Site.Master file into a temporary assembly under your "ASP.NET Temporary Files" folder. this is a reference to the "generated" class.
Assembly.GetExecutingAssembly() is basically the same as Assembly.GetAssembly(this.GetType()) (except it also works when there is no "this" (e.g. in static methods).
The best way would be use explicity use a type that you know exists in the assembly you're after. As an example, I assume your "Site.Master" has a code-behind file that is compiled into the assembly. You can use that instead:
Assembly.GetAssembly(typeof(Site)).GetName().Version.ToString()
(assuming the name of the class is Site)
Just as another solution that people may be interested in, I've concocted these helpers to help with this problem:
public static class HtmlHelperExtensions
{
private static string _CachedCurrentVersionDate;
/// <summary>
/// Return the Current Version from the AssemblyInfo.cs file.
/// </summary>
public static string CurrentVersion(this HtmlHelper helper)
{
try
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
return version.ToString();
}
catch
{
return "?.?.?.?";
}
}
public static string CurrentVersionDate(this HtmlHelper helper)
{
try
{
if (_CachedCurrentVersionDate == null)
{
// Ignores concurrency issues - assuming not locking this is faster than
// locking it, and we don't care if it's set twice to the same value.
var version = Assembly.GetExecutingAssembly().GetName().Version;
var ticksForDays = TimeSpan.TicksPerDay * version.Build; // days since 1 January 2000
var ticksForSeconds = TimeSpan.TicksPerSecond * 2 * version.Revision; // seconds since midnight, (multiply by 2 to get original)
_CachedCurrentVersionDate = new DateTime(2000, 1, 1).Add(new TimeSpan(ticksForDays + ticksForSeconds)).ToString();
}
return _CachedCurrentVersionDate;
}
catch
{
return "Unknown Version Date";
}
}
}
This allows consumption as follows in your footer:
Version: <%= Html.CurrentVersion() %> from <%= Html.CurrentVersionDate() %>
You can:
e.g in your Application_Start method in Global.asax file add
protected void Application_Start(object sender, EventArgs e)
{
HttpContext.Current.Application.Add("Version", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
}
in HTML show it by
<div><% =HttpContext.Current.Application["Version"].ToString() %></div>
ALSO Change Assembly version to 1.0.0.* by going to
- Project properties > Application > Assembly Information and assembly version is shown as 1.0.0.0 - change it to 1.0.0.*
this will give you some versioning
If you already have Global.asax in place, it could be a good place to store version globally once.
Global.asax.cs:
public class Global : HttpApplication
{
public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version;
}
Your view:
<div>- Version: #YourNamespace.Global.Version</div>
Related
I am trying to create a generic controller like this:
[Route("api/[controller]")]
public class OrdersController<T> : Controller where T : IOrder
{
[HttpPost("{orderType}")]
public async Task<IActionResult> Create(
[FromBody] Order<T> order)
{
//....
}
}
I intend for the {orderType} URI segment variable to control the generic type of the controller. I'm experimenting with both a custom IControllerFactory and IControllerActivator, but nothing is working. Every time I try to send a request, I get a 404 response. The code for my custom controller factory (and activator) is never executed.
Evidently the problem is that ASP.NET Core expects valid controllers to end with the suffix "Controller", but my generic controller instead has the (reflection based) suffix "Controller`1". Thus the attribute-based routes it declares are going unnoticed.
In ASP.NET MVC, at least in its early days, the DefaultControllerFactory was responsible for discovering all the available controllers. It tested for the "Controller" suffix:
The MVC framework provides a default controller factory (aptly named DefaultControllerFactory) that will search through all the assemblies in an appdomain looking for all types that implement IController and whose name ends with "Controller."
Apparently, in ASP.NET Core, the controller factory no longer has this responsibility. As I stated earlier, my custom controller factory executes for "normal" controllers, but is never invoked for generic controllers. So there is something else, earlier in the evaluation process, which governs the discovery of controllers.
Does anyone know what "service" interface is responsible for that discovery? I don't know the customization interface or "hook" point.
And does anyone know of a way to make ASP.NET Core "dump" the names of all the controllers it discovered? It would be great to write a unit test that verifies that any custom controller discovery I expect is indeed working.
Incidentally, if there is a "hook" which allows generic controller names to be discovered, it implies that route substitutions must also be normalized:
[Route("api/[controller]")]
public class OrdersController<T> : Controller { }
Regardless of what value for T is given, the [controller] name must remain a simple base-generic name. Using the above code as an example, the [controller] value would be "Orders". It would not be "Orders`1" or "OrdersOfSomething".
Note
This problem could also be solved by explicitly declaring the closed-generic types, instead of generating them at run time:
public class VanityOrdersController : OrdersController<Vanity> { }
public class ExistingOrdersController : OrdersController<Existing> { }
The above works, but it produces URI paths that I don't like:
~/api/VanityOrders
~/api/ExistingOrders
What I had actually wanted was this:
~/api/Orders/Vanity
~/api/Orders/Existing
Another adjustment gets me the URI's I'm looking for:
[Route("api/Orders/Vanity", Name ="VanityLink")]
public class VanityOrdersController : OrdersController<Vanity> { }
[Route("api/Orders/Existing", Name = "ExistingLink")]
public class ExistingOrdersController : OrdersController<Existing> { }
However, although this appears to work, it does not really answer my question. I would like to use my generic controller directly at run-time, rather than indirectly (via manual coding) at compile-time. Fundamentally, this means I need ASP.NET Core to be able to "see" or "discover" my generic controller, despite the fact that its run-time reflection name does not end with the expected "Controller" suffix.
What happens by default
During the controller discovery process, your open generic Controller<T> class will be among the candidate types. But the default implementation of the IApplicationFeatureProvider<ControllerFeature> interface, DefaultControllerTypeProvider, will eliminate your Controller<T> because it rules out any class with open generic parameters.
Why overriding IsController() doesn't work
Replacing the default implementation of the IApplicationFeatureProvider<ControllerFeature> interface, in order to override DefaultControllerTypeProvider.IsController(), will not work. Because you don't actually want the discovery process to accept your open generic controller (Controller<T>) as a valid controller. It is not a valid controller per se, and the controller factory wouldn't know how to instantiate it anyway, because it wouldn't know what T is supposed to be.
What needs to be done
1. Generate closed controller types
Before the controller discovery process even starts, you need to generate closed generic types from your open generic controller, using reflection. Here, with two sample entity types, named Account and Contact:
Type[] entityTypes = new[] { typeof(Account), typeof(Contact) };
TypeInfo[] closedControllerTypes = entityTypes
.Select(et => typeof(Controller<>).MakeGenericType(et))
.Select(cct => cct.GetTypeInfo())
.ToArray();
We now have closed TypeInfos for Controller<Account> and Controller<Contact>.
2. Add them to an application part and register it
Application parts are usually wrapped around CLR assemblies, but we can implement a custom application part providing a collection of types generated at runtime. We simply need to have it implement the IApplicationPartTypeProvider interface. Therefore, our runtime-generated controller types will enter the controller discovery process like any other built-in type would.
The custom application part:
public class GenericControllerApplicationPart : ApplicationPart, IApplicationPartTypeProvider
{
public GenericControllerApplicationPart(IEnumerable<TypeInfo> typeInfos)
{
Types = typeInfos;
}
public override string Name => "GenericController";
public IEnumerable<TypeInfo> Types { get; }
}
Registration in MVC services (Startup.cs):
services.AddMvc()
.ConfigureApplicationPartManager(apm =>
apm.ApplicationParts.Add(new GenericControllerApplicationPart(closedControllerTypes)));
As long as your controller derives from the built-in Controller class, there is no actual need to override the IsController method of the ControllerFeatureProvider. Because your generic controller inherits the [Controller] attribute from ControllerBase, it will be accepted as a controller in the discovery process regardless of its somewhat bizarre name ("Controller`1").
3. Override the controller name in the application model
Nevertheless, "Controller`1" is not a good name for routing purposes. You want each of your closed generic controllers to have independent RouteValues. Here, we will replace the name of the controller with that of the entity type, to match what would happen with two independent "AccountController" and "ContactController" types.
The model convention attribute:
public class GenericControllerAttribute : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
Type entityType = controller.ControllerType.GetGenericArguments()[0];
controller.ControllerName = entityType.Name;
}
}
Applied to the controller class:
[GenericController]
public class Controller<T> : Controller
{
}
Conclusion
This solution stays close to the overall ASP.NET Core architecture and, among other things, you will keep full visibility of your controllers through the API Explorer (think "Swagger").
It has been tested successfully with both conventional and attribute-based routing.
Short Answer
Implement IApplicationFeatureProvider<ControllerFeature>.
Question and Answer
Does anyone know what "service" interface is responsible for [discovering all available controllers]?
The ControllerFeatureProvider is responsible for that.
And does anyone know of a way to make ASP.NET Core "dump" the names of all the controllers it discovered?
Do that within ControllerFeatureProvider.IsController(TypeInfo typeInfo).
Example
MyControllerFeatureProvider.cs
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace CustomControllerNames
{
public class MyControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
var isController = base.IsController(typeInfo);
if (!isController)
{
string[] validEndings = new[] { "Foobar", "Controller`1" };
isController = validEndings.Any(x =>
typeInfo.Name.EndsWith(x, StringComparison.OrdinalIgnoreCase));
}
Console.WriteLine($"{typeInfo.Name} IsController: {isController}.");
return isController;
}
}
}
Register it during startup.
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(new MyControllerFeatureProvider());
});
}
Here is some example output.
MyControllerFeatureProvider IsController: False.
OrdersFoobar IsController: True.
OrdersFoobarController`1 IsController: True.
Program IsController: False.
<>c__DisplayClass0_0 IsController: False.
<>c IsController: False.
And here is a demo on GitHub. Best of luck.
Edit - Adding Versions
.NET Version
> dnvm install "1.0.0-rc2-20221" -runtime coreclr -architecture x64 -os win -unstable
NuGet.Config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear/>
<add key="AspNetCore"
value="https://www.myget.org/F/aspnetvnext/api/v3/index.json" />
</packageSources>
</configuration>
.NET CLI
> dotnet --info
.NET Command Line Tools (1.0.0-rc2-002429)
Product Information:
Version: 1.0.0-rc2-002429
Commit Sha: 612088cfa8
Runtime Environment:
OS Name: Windows
OS Version: 10.0.10586
OS Platform: Windows
RID: win10-x64
Restore, Build, and Run
> dotnet restore
> dotnet build
> dotnet run
Edit - Notes on RC1 vs RC2
This might not be possible is RC1, because DefaultControllerTypeProvider.IsController() is marked as internal.
Application Feature Providers examine application parts and provide features for those parts. There are built-in feature providers for the following MVC features:
Controllers
Metadata Reference
Tag Helpers
View Components
Feature providers inherit from IApplicationFeatureProvider, where T is the type of the feature. You can implement your own feature providers for any of MVC's feature types listed above. The order of feature providers in the ApplicationPartManager.FeatureProviders collection can be important, since later providers can react to actions taken by previous providers.
By default, ASP.NET Core MVC ignores generic controllers (for example, SomeController). This sample uses a controller feature provider that runs after the default provider and adds generic controller instances for a specified list of types (defined in EntityTypes.Types):
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
// This is designed to run after the default ControllerTypeProvider,
// so the list of 'real' controllers has already been populated.
foreach (var entityType in EntityTypes.Types)
{
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
// There's no 'real' controller for this entity, so add the generic version.
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType.AsType()).GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}
The entity types:
public static class EntityTypes
{
public static IReadOnlyList<TypeInfo> Types => new List<TypeInfo>()
{
typeof(Sprocket).GetTypeInfo(),
typeof(Widget).GetTypeInfo(),
};
public class Sprocket { }
public class Widget { }
}
The feature provider is added in Startup:
services.AddMvc()
.ConfigureApplicationPartManager(p =>
p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
By default, the generic controller names used for routing would be of the form GenericController`1[Widget] instead of Widget. The following attribute is used to modify the name to correspond to the generic type used by the controller:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;
namespace AppPartsSample
{
// Used to set the controller name for routing purposes. Without this convention the
// names would be like 'GenericController`1[Widget]' instead of 'Widget'.
//
// Conventions can be applied as attributes or added to MvcOptions.Conventions.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericController<>))
{
// Not a GenericController, ignore.
return;
}
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name;
}
}
}
The GenericController class:
using Microsoft.AspNetCore.Mvc;
namespace AppPartsSample
{
[GenericControllerNameConvention] // Sets the controller name based on typeof(T).Name
public class GenericController<T> : Controller
{
public IActionResult Index()
{
return Content($"Hello from a generic {typeof(T).Name} controller.");
}
}
}
Sample: Generic controller feature
To get a list of controllers in RC2, just get ApplicationPartManager from DependencyInjection and do this:
ApplicationPartManager appManager = <FROM DI>;
var controllerFeature = new ControllerFeature();
appManager.PopulateFeature(controllerFeature);
foreach(var controller in controllerFeature.Controllers)
{
...
}
I'm translating a web application and things are generally going smoothly with wicket:message and properties files. But Wicket always wants to have a component for looking up strings.
How can I translate converters and renderers (i.e. implementations of IConverter and IChoiceRenderer) which don't have access to any Wicket component in their methods?
So far I found one way - Application.get().getResourceSettings().getLocalizer().getString(key, null) - but I have to make the strings "global", i.e. associated with the application class. That's not nice for separation and reuse. How can I do it better?
I think you should invent you own way how to achieve this. Here in my current project we registered our own IStringResourceLoader like this:
IStringResourceLoader stringResourceLoader = new OurOwnResourceLoaderImpl();
Application.get().getResourceSettings().getStringResourceLoaders().add(stringResourceLoader);
Then for example in IChoiceRenderer we just call Application.get().getLocalizer().getString("key", null).
Inside our IStringResourceLoader we are looking for bundles (property files) with some string pattern according our own conventions.
Or you can just register localization bundle (ie. properties file) distributed inside your library's jar in Application#init through org.apache.wicket.resource.loader.BundleStringResourceLoader.
Afaik there is no standard way to do that so it's up to you what path you choose.
Updated:
I found another solution how your library/extension can register it's own localization by itself so you needn't to touch Application#init or create your own IStringResourceLoaders.
There is preregistered string resource loader org.apache.wicket.resource.loader.InitializerStringResourceLoader (see wickets default IResourceSetting implementation ie. ResourceSetting and it's constructor) which uses wicket's Initializer mechanism - see IInitializer javadoc - basically you add wicket.properties file in your jar class root (ie. it is in default/none package) and inside file there is:
initializer=i.am.robot.MyInitilizer
then i.am.robot.MyInitilizer:
public class MyInitializer implements IInitializer {
/**
* #param application
* The application loading the component
*/
void init(Application application) {
// do whatever want
}
/**
* #param application
* The application loading the component
*/
void destroy(Application application) {
}
}
and now you create your localization bundles in same package and same name as IInitializer implementation (in our example MyInitializer)
I think I found another way...
I noticed that IStringResourceLoader also has a method String loadStringResource(Class<?> clazz, String key, Locale locale, String style); (and one more parameter for variation in newer Wicket versions) which does not require a component. clazz is supposed to be a component class, but... it doesn't actually have to be :)
I was able to implement my own class MyLocalizer extends Localizer with a new method
getString(String key, Class<?> cl, IModel<?> model, Locale locale, String defaultValue)
which works in a similar way to
getString(String key, Component component, IModel<?> model, String defaultValue)
but uses the class directly instead of a component. It still uses the same properties cache and resource loaders.
Then I wrote an abstract class MyConverter implements IConverter which has a MyLocalizer getLocalizer() and a few getString methods like the Component class. Basically it does getLocalizer().getString(key, getClass(), model, locale, defaultValue), so the properties can now be attached to the converter class.
Seems to work :)
If I understand your question...
You can use package based properties that means if you put your keys/values into a property file 'package.properties' in a package. Each localized resource of any subpackage under that package returns the value associated to the requested key until you override it in another property file.
The file name is 'package.properties' in Wicket prior to 1.6.x and 'wicket-package.properties' in Wicket 1.6+
See
https://cwiki.apache.org/confluence/display/WICKET/Migration+to+Wicket+6.0#MigrationtoWicket6.0-package.propertiesrenamedtowicket-package.properties
However it works just for componet, outside the componet (when component argument is null), it is possible to use:
WicketApplication.properties (the WebApplication class is WicketApplication.class, this property file is in the same package).
applicationGlobalProperty=My Global Localized Property
wicket-package.properties (package based, place it in the same package as the page)
localText=Localized text: A local component text based on wicket-package.properties
LocalizedPage.html (markup template)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Localized Page</title>
</head>
<body xmlns:wicket="http://wicket.apache.org">
<div>
<div>
<h2>Texts</h2>
<div>
<wicket:message key="localText"/> <br/>
<span wicket:id="localizedLabel"></span>
</div>
</div>
</div>
</body>
</html>
LocalizePage.java (code)
public class LocalizedPage extends WebPage {
private static final long serialVersionUID = 1L;
public LocalizedPage() {
super();
}
#Override
protected void onInitialize() {
super.onInitialize();
add(new Label("localizedLabel", new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
#Override
public String getObject() {
return WicketApplication.get().getResourceSettings().getLocalizer().getString("applicationGlobalProperty", null);
}
}));
}
}
See the full example on https://repo.twinstone.org/projects/WISTF/repos/wicket-examples-6.x/browse
My company has got a deployment policy (I skip the details) such that any 3rd party software should be installed in the GAC, whilst our libraries are in Web/bin folder. But this approach doesn't work with Ninject and MVC 3/4. Let's follow an example:
This is my dependencies binding code:
public class RequestorDependenciesRegistration : NinjectModule
{
public override void Load()
{
Bind<IMyDearDependency>().To<MyDearImplementation>();
}
}
And this is my MVC controller:
public MyController(IMyDearDependency something) {
this.something = something; // 'something' is set only if Ninject dlls are in Web/bin... X-(
}
If Ninject dlls are in the GAC, it loads the module correctly, but when instantiating the MVC Controller the dependency is not injected (in some cases is null, in some cases MVC returns an error "No parameterless constructor etc etc"). If I manually copy Ninject*.dll in the Web/bin folder, than everything works fine, even without restarting IIS! Can't really understand why...
Even more surprisingly (for me), if I do something super-dirty like storing a reference to the Ninject Kernel instance in a public static property and use it as a ServiceLocator, it works! (Something dirty like this, in the MVC controller):
public MyController(IMyDearDependency something) { // 'something' is always null if Ninject is only in the GAC...
var controller = Kernel.Get<MyController>()
this.something = controller.something; // ... but this 'controller.something' is set, even if Ninject is only in the GAC!!! 8-O
}
Can anyone suggest me the reason why? And possibly a solution? :-) Many thanks!!
Ninject has a built in extension loading mechanism which is used to load the different extension like the Ninject.Web.Mvc.
But mechanism is looking only for the application folder to load the extensions so if your dll are in the GAC Ninject won't find them.
To solve this you can turn off the automatic extension loading and load the MvcModule module by hand when creating your StandardKernel:
var _kernel = new StandardKernel(
new NinjectSettings() { LoadExtensions = false },
new MvcModule(),
/* your other modules * /);
I have MVC areas in external libraries which have their own area registration code just as a normal MVC area would. This area registration gets called for each dll (module) and I have verified the RouteTable contains all the routes from the loaded modules once loading has been completed.
When I reference these external areas in the main site they get pulled into the bin directory and load up fine. That is, when a request is made for a route that exists in an external library, the correct type is passed to my custom controller factory (Ninject) and the controller can be instantiated.
Once I move these dll's outside of the bin directory however (say to a Modules folder), there appears to be an issue with routing. I have checked that the RouteTable has all the required routes but by the time a request makes its way into the ninject controller factory the requested type is null. From reading here an SO link here this behaviour seems to occur when ASP.NET MVC cannot find the controller matching the requested route or does not know how to make sense of the route.
When loading the modules externally I have ensured that the modules that I want loaded are loaded into the app domain via a call to Assemby.LoadFrom(modulePath);
I did some research and it appears that when attempting to load a library outside of bin you need to specify private probing in app.config as pointed out here;. I have mine set to 'bin\Modules' which is where the mvc area modules get moved too.
Does anyone have any ideas why simply moving an mvc area project outside of the bin folder would cause the requested type passed into the controller factory to be null resulting in the controller to be instantiated?
Edit:
All routes registered in external areas have the namespace of the controller specified in the route
Below is a fragment of code that creates a new Ninject kernel, reads a list of module names from a file to enable, and then goes searching for the enabled modules in the bin/Modules directory. The module is loaded via the assembly loader, has its area(s) registered and then loaded into the ninject kernel.
// comma separated list of modules to enable
string moduleCsv = ConfigurationManager.AppSettings["Modules.Enabled"];
if (!string.IsNullOrEmpty(moduleCsv)) {
string[] enabledModuleList = moduleCsv.Replace(" ", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
_Modules = enabledModuleList ?? new string[0];
// load enabled modules from bin/Modules.
var moduleList = Directory.GetFiles(Server.MapPath("~" + Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar + "Modules"), "*.dll");
foreach (string enabledModule in enabledModuleList) {
string modulePath = moduleList.Single(m => m.Contains(enabledModule));
// using code adapted from from AssemblyLoader
var asm = AssemblyLoader.LoadAssembly(modulePath);
// register routes for module
AreaRegistrationUtil.RegisterAreasForAssemblies(asm);
// load into Ninject kernel
kernel.Load(asm);
}
}
This is the crux of the Ninject controller factory that receives the aforementioned Ninject kernel and handles requests to make controllers. For controllers that exist within an assembly in bin/Modules the GetControllerType(...) returns null for the requested controller name.
public class NinjectControllerFactory : DefaultControllerFactory
{
#region Instance Variables
private IKernel _Kernel;
#endregion
#region Constructors
public NinjectControllerFactory(IKernel kernel)
{
_Kernel = kernel;
}
protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
{
// Is null for controller names requested outside of bin directory.
var type = base.GetControllerType(requestContext, controllerName);
return type;
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
IController controller = null;
if (controllerType != null)
controller = _Kernel.Get(controllerType) as IController;
return controller;
}
}
Update on Ninject Nuget Install
I couldn't get it to install Ninject.MVC3 via NuGet for some reason. Visual Studio was giving some schemaVersion error when clicking the install button (I have installed other Nuget packages like ELMAH btw).
I did find out something else that was interesting though, and that is that if I pass in the extra module assembilies to the NinjectControllerFactory I have and search those when the type cannot be resolved it finds the correct type and is able to build the controller. This leads to another strange problem.
The first route to be requested from an external module is the /Account/LogOn in the auth and registration module. The virtual path provider throws an error here after it has located the view and attempts to render it out complaining of a missing namespace. This causes an error route to fire off which is handled by an ErrorHandling module. Strangely enough, this loads and render fine!
So I am still stuck with two issues;
1) Having to do a bit of a dodgy hack and pass in the extra module assemblies to the NinjectControllerFactory in order to be able to resolve types for Controllers in external modules
2) An error with one particular module where it complains about a namespace not being found
These two issues are obviously connected because the assembly loading just isn't loading up and making everything available that needs to be. If all these mvc areas are loaded from the bin directory everything works fine. So it is clearly a namespacing/assembly load issue.
LoadFrom load the assembly into the loading context. These types are not available to the other classes in the default Load context. Probably this is the reason why the controller is not found.
If you know which assemblies have to be loaded then you should always use Assembly.Load(). If you don't know which assemblies are depolyed in the directory then either guess from the filesnames the assembly names or use Assembly.ReflectionOnlyLoadFrom() (preferably using a temporary AppDomain) to get the assembly names. Then load the assemblies using Assembly.Load() with the assembly name.
If your assemblies contain NinjectModules you can also use kernel.Load() which does what I described above. But it only loads assemblies containing at least one module.
Read up http://msdn.microsoft.com/en-us/library/dd153782.aspx about the different assembly contexts.
Here is a small extract from the Ninject codebase. I removed the unnecessary stuff but did not try to compile or run so probably there are minor issues with this.
public class AssemblyLoader
{
public void LoadAssemblies(IEnumerable<string> filenames)
{
GetAssemblyNames(filenames).Select(name => Assembly.Load(name));
}
private static IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames)
{
var temporaryDomain = CreateTemporaryAppDomain();
try
{
var assemblyNameRetriever = (AssemblyNameRetriever)temporaryDomain.CreateInstanceAndUnwrap(typeof(AssemblyNameRetriever).Assembly.FullName, typeof(AssemblyNameRetriever).FullName);
return assemblyNameRetriever.GetAssemblyNames(filenames.ToArray());
}
finally
{
AppDomain.Unload(temporaryDomain);
}
}
private static AppDomain CreateTemporaryAppDomain()
{
return AppDomain.CreateDomain(
"AssemblyNameEvaluation",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
}
private class AssemblyNameRetriever : MarshalByRefObject
{
public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames)
{
var result = new List<AssemblyName>();
foreach(var filename in filenames)
{
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(filename);
}
catch (BadImageFormatException)
{
// Ignore native assemblies
continue;
}
result.Add(assembly.GetName(false));
}
return result;
}
}
}
Recently I've switched to Ninject 2.0 release and started getting the following error:
Error occured: Error activating SomeController
More than one matching bindings are available.
Activation path:
1) Request for SomeController
Suggestions:
1) Ensure that you have defined a binding for SomeController only once.
However, I'm unable to find certain reproduction path. Sometimes it occurs, sometimes it does not.
I'm using NinjectHttpApplication for automatic controllers injection. Controllers are defined in separate assembly:
public class App : NinjectHttpApplication
{
protected override IKernel CreateKernel()
{
INinjectModule[] modules = new INinjectModule[] {
new MiscModule(),
new ProvidersModule(),
new RepositoryModule(),
new ServiceModule()
};
return new StandardKernel(modules);
}
protected override void OnApplicationStarted()
{
RegisterRoutes(RouteTable.Routes);
RegisterAllControllersIn("Sample.Mvc");
base.OnApplicationStarted();
}
/* ............. */
}
Maybe someone is familiar with this error.
Any advice?
I finally figured this issue out recently. Apparently, the NinjectHttpApplication.RegisterAllControllersIn() function doesn't do all of the proper bindings needed. It binds your concrete controller implementations to IController requests. For example, if you have a controller class called SampleMvcController, which inherits from System.Web.Mvc.Controller. It would do the following named binding during application start:
kernel.Bind<IController>().To(SampleMvcController).InTransientScope().Named("SampleMvc");
But when debugging the NinjectControllerFactory, I find that request are being made for the Ninject Kernel to return an object for the class "SampleMvcController", not for a concrete implementation of IController, using the named binding of "SampleMvc".
Because of this, when the first web request that involves the SampleMvcController is made, it creates a binding of SampleMvcController to itself. This is not thread safe though. So if you have several web requests being made at once, the bindings can potentially happen more than once, and now you are left with this error for having multiple bindings for the SampleMvcController.
You can verify this by quickly refreshing an MVC URL, right after causing your web application to restart.
The fix:
The simplest way to fix this issue is to create a new NinjectModule for your controller bindings, and to load this module during application start. Within this module, you self bind each of your defined controllers, like so:
class ControllerModule : StandardModule {
public override Load() {
Bind<SampleMvcController>().ToSelf();
Bind<AnotherMvcController>().ToSelf();
}
}
But if you don't mind changing the Ninject source code, you can modify the RegisterAllControllersIn() function to self bind each controller it comes across.
I have been dealing with this problem for months. I tried so many options but was unable to come to a solution. I knew that it was a threading problem because it would only occur when there was a heavy load on my site. Just recently a bug was reported and fixed in the ninject source code that solves this problem.
Here is a reference to the issue. It was fixed in build 2.1.0.70 of the Ninject source. The key change was in KernelBase.cs by removing the line
context.Plan = planner.GetPlan(service);
and replacing it with
lock (planner)
{
context.Plan = planner.GetPlan(service);
}
To use this new build with MVC you will need to get the latest build of Ninject then get the latest build of ninject.web.mvc. Build ninject.web.mvc with the new Ninject build.
I have been using this new build for about a week with a heavy load and no problems. That is the longest it has gone without a problem so I would consider this to be a solution.
Are you sure you really are creating a single completely new Kernel from scratch in your OnApplicationStarted every time it's invoked ? If you're not and you're actually creating it once but potentially running the registration bit twice. Remember that you're not guaranteed to only ever have one App class instantiated ever within a given AppDomain.
My answer was a bit more obvious.
I had declared the binding for one of my controllers more than once during refactor of my code.
I added this to my global.ascx.cs file:
public void RegisterAllControllersInFix(Assembly assembly)
{
RegisterAllControllersInFix(assembly, GetControllerName);
}
public void RegisterAllControllersInFix(Assembly assembly, Func<Type, string> namingConvention)
{
foreach (Type type in assembly.GetExportedTypes().Where(IsController))
Kernel.Bind(type).ToSelf();
}
private static bool IsController(Type type)
{
return typeof(IController).IsAssignableFrom(type) && type.IsPublic && !type.IsAbstract && !type.IsInterface;
}
private static string GetControllerName(Type type)
{
string name = type.Name.ToLowerInvariant();
if (name.EndsWith("controller"))
name = name.Substring(0, name.IndexOf("controller"));
return name;
}
Then called it from my OnApplicationStarted() method as follows:
RegisterAllControllersIn(Assembly.GetExecutingAssembly());
RegisterAllControllersInFix(Assembly.GetExecutingAssembly());
Difficult to know whether this fixed it though because it's so intermittent.