A MEF Plugin for MVC5 - asp.net-mvc

I'm using MEF to create a MVC 5 app that support plugins. Using a IControllerFactory and IOC from MEF I've got it working so that /app/specialcontroller/action calls the action and controller from the other dll (plugin).
The problem i have is that when the bundles and content are loaded, the controller factory is failing because content and bundles aren't exported from the plugin.
What I would like to happen is to use the Default factory for certain "controller" names.
Is this possible?
This is the answer I based it on...
MEF with MVC 4 or 5 - Pluggable Architecture (2014)
When including app/bundles/jquery I want it to use the MVC controller for bundles
Hope that makes sense, thanks

In this scenario I have used the following controller factory in one of my projects earlier -
namespace CaterPiller.WebClient.Factories
{
public class MvcControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _container;
public MvcControllerFactory(CompositionContainer container)
{
_container = container;
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if(controllerType == null)
{
return null;
}
var export = _container.GetExports(controllerType, null, null).FirstOrDefault();
return null == export
? base.GetControllerInstance(requestContext, controllerType)
: (IController) export.Value;
}
public override void ReleaseController(IController controller)
{
((IDisposable) controller).Dispose();
}
}
}
for bundles and content, the controller type is usually null, so I just returned null for them -
if(controllerType == null)
{
return null;
}
and got them served by the default controller. I had my other urls served by controllers only bundles and contents were not, so this worked for me. Probably not the best solution but this solved my problem back then while using MEF for creating controller factory. You can try this solution and this might help you.

Related

Dependency injection in constructor of controller with using a ControllerFactory in ASP.NET MVC 5

I'm developing ASP.NET MVC 5 app. I need to use parameters in controller's constructor. DefaultControllerFactory can't resolve it and i inherited from it my own ControllerFactory:
public class ControllerFactoryProvider : DefaultControllerFactory
{
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
string controllerType = string.Empty;
IController controller = null;
// Read Controller Class & Assembly Name from Web.Config
controllerType = ConfigurationManager.AppSettings[controllerName];
if (controllerType == null)
throw new ConfigurationErrorsException("Assembly not configured for controller " + controllerName);
// Create Controller Instance
IDataTransmitter _dataTransmitter = new DataTransmitter();
controller = Activator.CreateInstance(Type.GetType(controllerType), _dataTransmitter) as IController;
return controller;
}
public void ReleaseController(IController controller)
{
//This is a sample implementation
//If pooling is used to write code to return the object to pool
if (controller is IDisposable)
{
(controller as IDisposable).Dispose();
}
controller = null;
}
}
I registered it in Global.asax:
ControllerBuilder.Current.SetControllerFactory(new
ControllerFactoryProvider());
But when i run my app it whatever use DefaultControllerFactory didn't see constructor with parameters.
Where can i have an error?
As I said in the comments, there is no need to override your controller factory. You just need to plug in your preferred dependency injection container.
I haven't had the opportunity to work with every dependency injection containers for .net but I'll try to give an objective answer.
Ninject
To set up Ninject in an asp.net Mvc 5 project is very straight forward.
Installing the nuget package
There's a very handy nuget package called Ninject.MVC5.
You can install it:
Using the manage nuget packages dialogue, or
By running Install-Package Ninject.MVC5 in the package manager console.
After installing Ninject.MVC5 you will see a new file in your solution in App_Start/ called NinjectWebCommon.cs. Here you can see what the contents of that file will end up being.
Wiring up your dependencies
Now that the package is installed you want to register your denpencies using ninject's api.
Let's say you have an IFoo interface and its implementation Foo
public interface IFoo
{
int Bar()
}
public class Foo : IFoo
{
public int Bar()
{
throw new NotImplementedException();
}
}
In your NinjectWebCommon class you're going to tell ninject how to resolve an IFoo interface:
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IFoo>().To<Foo>();
}
Keep in mind that by default Ninject has implicit self binding of concrete types, that means that
If the type you’re resolving is a concrete type (like Foo above), Ninject will automatically create a default association via a mechanism called implicit self binding. It’s as if there’s a registration like this:
Bind<Foo>().To<Foo>();

MapMvcAttributeRoutes: This method cannot be called during the application's pre-start initialization phase

I have a very simple test in a test project in a solution using ASP MVC V5 and attribute routing. Attribute routing and the MapMvcAttributeRoutes method are part of ASP MVC 5.
[Test]
public void HasRoutesInTable()
{
var routes = new RouteCollection();
routes.MapMvcAttributeRoutes();
Assert.That(routes.Count, Is.GreaterThan(0));
}
This results in:
System.InvalidOperationException :
This method cannot be called during the applications pre-start initialization phase.
Most of the answers to this error message involve configuring membership providers in the web.config file. This project has neither membership providers or a web.config file so the error seems be be occurring for some other reason. How do I move the code out of this "pre-start" state so that the tests can run?
The equivalent code for attributes on ApiController works fine after HttpConfiguration.EnsureInitialized() is called.
I recently upgraded my project to ASP.NET MVC 5 and experienced the exact same issue. When using dotPeek to investigate it, I discovered that there is an internal MapMvcAttributeRoutes extension method that has a IEnumerable<Type> as a parameter which expects a list of controller types. I created a new extension method that uses reflection and allows me to test my attribute-based routes:
public static class RouteCollectionExtensions
{
public static void MapMvcAttributeRoutesForTesting(this RouteCollection routes)
{
var controllers = (from t in typeof(HomeController).Assembly.GetExportedTypes()
where
t != null &&
t.IsPublic &&
t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
!t.IsAbstract &&
typeof(IController).IsAssignableFrom(t)
select t).ToList();
var mapMvcAttributeRoutesMethod = typeof(RouteCollectionAttributeRoutingExtensions)
.GetMethod(
"MapMvcAttributeRoutes",
BindingFlags.NonPublic | BindingFlags.Static,
null,
new Type[] { typeof(RouteCollection), typeof(IEnumerable<Type>) },
null);
mapMvcAttributeRoutesMethod.Invoke(null, new object[] { routes, controllers });
}
}
And here is how I use it:
public class HomeControllerRouteTests
{
[Fact]
public void RequestTo_Root_ShouldMapTo_HomeIndex()
{
// Arrange
var routes = new RouteCollection();
// Act - registers traditional routes and the new attribute-defined routes
RouteConfig.RegisterRoutes(routes);
routes.MapMvcAttributeRoutesForTesting();
// Assert - uses MvcRouteTester to test specific routes
routes.ShouldMap("~/").To<HomeController>(x => x.Index());
}
}
One problem now is that inside RouteConfig.RegisterRoutes(route) I cannot call routes.MapMvcAttributeRoutes() so I moved that call to my Global.asax file instead.
Another concern is that this solution is potentially fragile since the above method in RouteCollectionAttributeRoutingExtensions is internal and could be removed at any time. A proactive approach would be to check to see if the mapMvcAttributeRoutesMethod variable is null and provide an appropriate error/exceptionmessage if it is.
NOTE: This only works with ASP.NET MVC 5.0. There were significant changes to attribute routing in ASP.NET MVC 5.1 and the mapMvcAttributeRoutesMethod method was moved to an internal class.
In ASP.NET MVC 5.1 this functionality was moved into its own class called AttributeRoutingMapper.
(This is why one shouldn't rely on code hacking around in internal classes)
But this is the workaround for 5.1 (and up?):
public static void MapMvcAttributeRoutes(this RouteCollection routeCollection, Assembly controllerAssembly)
{
var controllerTypes = (from type in controllerAssembly.GetExportedTypes()
where
type != null && type.IsPublic
&& type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
&& !type.IsAbstract && typeof(IController).IsAssignableFrom(type)
select type).ToList();
var attributeRoutingAssembly = typeof(RouteCollectionAttributeRoutingExtensions).Assembly;
var attributeRoutingMapperType =
attributeRoutingAssembly.GetType("System.Web.Mvc.Routing.AttributeRoutingMapper");
var mapAttributeRoutesMethod = attributeRoutingMapperType.GetMethod(
"MapAttributeRoutes",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(RouteCollection), typeof(IEnumerable<Type>) },
null);
mapAttributeRoutesMethod.Invoke(null, new object[] { routeCollection, controllerTypes });
}
Well, it's really ugly and I'm not sure if it'll be worth the test complexity, but here's how you can do it without modifying your RouteConfig.Register code:
[TestClass]
public class MyTestClass
{
[TestMethod]
public void MyTestMethod()
{
// Move all files needed for this test into a subdirectory named bin.
Directory.CreateDirectory("bin");
foreach (var file in Directory.EnumerateFiles("."))
{
File.Copy(file, "bin\\" + file, overwrite: true);
}
// Create a new ASP.NET host for this directory (with all the binaries under the bin subdirectory); get a Remoting proxy to that app domain.
RouteProxy proxy = (RouteProxy)ApplicationHost.CreateApplicationHost(typeof(RouteProxy), "/", Environment.CurrentDirectory);
// Call into the other app domain to run route registration and get back the route count.
int count = proxy.RegisterRoutesAndGetCount();
Assert.IsTrue(count > 0);
}
private class RouteProxy : MarshalByRefObject
{
public int RegisterRoutesAndGetCount()
{
RouteCollection routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes); // or just call routes.MapMvcAttributeRoutes() if that's what you want, though I'm not sure why you'd re-test the framework code.
return routes.Count;
}
}
}
Mapping attribute routes needs to find all the controllers you're using to get their attributes, which requires accessing the build manager, which only apparently works in app domains created for ASP.NET.
What are you testing here? Looks like you are testing a 3rd party extension method. You shouldn't be using your unit tests to test 3rd party code.

Best practice -- how to get back the internal binding from controller factory?

I am following "ASP.Net MVC 3" by Steven Sanderson and Adam Freeman, and at one point they define ControllerFactory. The exposed interface is for creating controllers, and what is injected into them (like classes providing data) is black box (for outside world).
I am at the point, that I don't really want to get any controller, but the binding set for controller -- namely class providing data.
I could add another method for controller factory (like GetBinding) and it would work, but would it be the right way to do it?
Just to focus on something. I have IDataProvider and two classes -- MockupProvider and ProviderForReal. I would like to set it once, that for now whenever I need IDataProvider I will get MockupProvider. This is set up (by me) in controller factory.
And I would like to retrieve what I set up in most elegant way, so I won't bind again interface-class again. Is adding such method -- GetBinding -- to controller factor a good pattern?
I am not constructing the controller, I need binding controllers use.
In other words...
There is controller factory. Inside there are defined some bindings. I have to use retrieve them (binding, not controller). Technically I could do this in several ways:
take a look at the code, look at specific binding, and use the the bound type (hardcoding it) somewhere else
add public method to controller factory GetBinding
...?
What is the right way?
Update
My controller factory:
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel ninject_kernel;
public NinjectControllerFactory()
{
ninject_kernel = new StandardKernel();
AddBindings();
}
private void AddBindings()
{
ninject_kernel.Bind<IBookRepository>().To<DataManagement.Concrete.EFBookRepository>();
// ninject_kernel.Bind<IBookRepository>().ToConstant(DataManagement.Mocks.Mocks.BookRepository);
}
public T GetBinding<T>()
{
return ninject_kernel.Get<T>();
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return null;
else
return (IController)ninject_kernel.Get(controllerType);
}
}
I'm trying to answer your questions following the comments. If it won't be suitable for you I'm prepared to delete it.
So in my ASP.NET MVC applications I'm using ninject and its mvc extension to inject dependencies to my controllers (and underlying services and repositories).
Global.asax
public class MvcApplication : Ninject.Web.Mvc.NinjectHttpApplication
{
/// this is here only to see that NinjectHttpApplication uses its own ControllerFactory, which is supposed to create your controllers with dependencies injected
protected override Ninject.Web.Mvc.NinjectControllerFactory CreateControllerFactory()
{
return base.CreateControllerFactory();
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
// here you can configure your bindings according to actual requirements
kernel.Bind<IDataProvider>().To<ProviderForReal>().InRequestScope();
kernel.Bind<IDataService>().To<RealDataService().InRequestScope();
return kernel;
}
}
Controller
public class MyController : Controller
{
private readonly IDataProvider dataService;
// i will get injected an IDataProvider according to my actual configuration
public MyController(IDataService dataService)
{
this.dataService = dataService;
}
}
IDataService
public class RealDataService: IDataService{
private readonly IDataProvider dataProvider;
public RealDataService(IDataProvider dataProvider){
this.dataProvider = dataProvider;
}
}
Update
You do not need to write your own controller factory. In the code above I have put my override of CreateControllerFactory method only to show that Ninject.Web.Mvc.NinjectHttpApplication implicitly overrides this method and uses its own NinjectControllerFactory implmentation which will resolve dependencies for you (even if that dependencies are indirect - as you can see in my updated code => Ninject will resolve it for you because it will see that MyController needs IDataService and will look into bindings and will see that there is binding to RealDataService, but it has only constructor with dependency on IDataProvider. So it will look again to bindings and will see that IDataProvider is bound to ProviderForReal than it will create ProviderForReal inject it to ReadDataService and than RealDataService to MyController).

Creating a custom ViewResult that displays a different view based on platform in ASP.NET MVC 3

I'm trying to make my Action return a different view for different platforms, respecting the routing config. If I create a custom ViewResult, will I override the FindView method? And if so, how can I modify the View that is automatically found?
For example: HomeController.About action would display View\Home\About.cshtml on computer, View\Home\AboutTablet.cshtml on a tablet, and View\Home\AboutMobile.cshtml on a cell phone
There's a NuGet for you: MobileViewEngines. ScottHa covered it in a blog post. It's spec compatible with ASP.NET MVC 4 where you can get rid of it easily because this functionality is built-in.
You could define an Actionfilter like this:
public class SetDeviceDependantView : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// Only works on ViewResults...
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null)
{
if (filterContext == null)
throw new ArgumentNullException("context");
// Default the viewname to the action name
if (String.IsNullOrEmpty(viewResult.ViewName))
viewResult.ViewName = filterContext.RouteData.GetRequiredString("action");
// Add suffix according to device type
if (IsTablet(filterContext.HttpContext))
viewResult.ViewName += "Tablet";
else if (IsMobile(filterContext.HttpContext))
viewResult.ViewName += "Mobile";
}
base.OnResultExecuting(filterContext);
}
private static bool IsMobile(HttpContextBase httpContext)
{
return httpContext.Request.Browser.IsMobileDevice;
}
private static bool IsTablet(HttpContextBase httpContext)
{
// this requires the 51degrees "Device Data" package: http://51degrees.mobi/Products/DeviceData/PropertyDictionary.aspx
var isTablet = httpContext.Request.Browser["IsTablet"];
return isTablet != null && isTablet.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase);
}
}
Then you can either annotate the required Actions / Controllers like this:
[SetDeviceDependantView]
public ActionResult About()
{
return View();
}
Or set it globally in the global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new SetDeviceDependantView());
}
Note, that I'm relying here on the 51degrees library to detect the tablet, you could consider using a different technique. However, that's a different topic.
If you want to do this pre-MVC4, check out this blog post from Christopher Bennage
http://dev.bennage.com/blog/2012/04/27/render-action/
I was specially interested in the ContentTypeAwareResult class, which looks like it might be what you are looking for.
https://github.com/liike/reference-application/blob/master/MileageStats.Web/ContentTypeAwareResult.cs
You'll have to create your own ViewEngine (probably deriving it from the one you're using) and override FindView and FindPartialView. You can provide fallbacks (i.e., use generic view if no tablet one is found).
Most trouble will be in defining the criteria to differentiate between different "modes".
Building a custom ViewEngine is a preferred approach in MVC3 for this requirement. For you info - MVC4 support this feature out of the box.
For more information about device specific view, a similar answer is posted here on StackOverflow itself https://stackoverflow.com/a/1387555/125651

ASP.NET MVC and Unity 1.2 Container question

I am trying to use the Unity container to make it easier to unit test my controllers. My controller uses a constructor that accepts an interface to a Repository. In the global.asax file, I instantiate a UnityContainerFactory and register it with the MVC framework and then register the repository and its implementation. I added the [Dependency] attribute to the controller’s CTOR Repository parameter. This all seems to work OK, except that occasionally the factory’s GetControllerInstance(Type controllerType) is called more than once and is passed a null argument as the controllerType.
The first call to the factory is aways correct and the controllerType “ProductsController” is passed-in as an argument. But sometimes, the factory is called a couple more times after the view has been displayed with a null value for the controller and I am not sure why. When the correct value of the controller type is passed that “Call Stack” makes sense to me, but when a null is passed, I am not sure why or who is making the call. Any ideas?
The code and call stacks for the example are shown below.
Call Stack when is works
Test.DLL!Test.UnityHelpers.UnityControllerFactory.GetControllerInstance(System.Type controllerType = {Name = "ProductsController" FullName = "Test.Controllers.ProductsController"}) Line 23 C#
Test.DLL!Test._Default.Page_Load(object sender = {ASP.default_aspx}, System.EventArgs e = {System.EventArgs}) Line 18 + 0x1a bytes C#
Call Stack when NULL is passed at the controllerType
Test.DLL!Test.UnityHelpers.UnityControllerFactory.GetControllerInstance(System.Type controllerType = null) Line 27 C#
First I created a UnityControllerFactory
public class UnityControllerFactory : DefaultControllerFactory
{
UnityContainer container;
public UnityControllerFactory(UnityContainer container)
{
this.container = container;
}
protected override IController GetControllerInstance(Type controllerType)
{
if (controllerType != null)
{
return container.Resolve(controllerType) as IController;
}
else
{
return null; // I never expect to get here, but I do sometimes, the callstack does not show the caller
}
}
}
Next, I added the following code the global.asax file to instantiate the container factory
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
// Create Unity Container if needed
if (_container == null)
{
_container = new UnityContainer();
}
// Instantiate a new factory
IControllerFactory unityControllerFactory = new UnityControllerFactory(_container);
// Register it with the MVC framework
ControllerBuilder.Current.SetControllerFactory(unityControllerFactory);
// Register the SqlProductRepository
_container.RegisterType<IProductsRepository, SqlProductRepository>
(new ContainerControlledLifetimeManager());
}
The app has one controller
public class ProductsController : Controller
{
public IProductsRepository productsRepository;
public ProductsController([Dependency]IProductsRepository productsRepository)
{
this.productsRepository = productsRepository;
}
}
This is likely due to some file type not mapping to a controller in your routes. (images, for example). This will happen more often when you are debugging locally with Cassini in my experience since Cassini allows all requests to route through ASP.NET while in IIS a lot of requests are handled by IIS for you. This would also be why you don't see your code in the stack for this request. If you turn off the "Just My Code" option in Visual Studio, you can sometimes get a better hint about these things.
This is not the only reason this can happen, though, but it's common.
The appropriate thing to do would be to allow the base method handle the request in these situations. It's usually just a simple file request and shouldn't have any impact on you.
Simplest thing to do would be to gate it like this:
if (controllerType != null)
{
return container.Resolve(controllerType) as IController;
}
else
{
return base.GetControllerInstance(requestContext, controllerType);
}
That ought to do it.
To see what the request is for, you might be able to check HttpContext.Current.Request to see what file is not in your route. A lot of times it's not something you care to control, but it'll make you feel better to know what the origin of the request is.

Resources