Hi,
I'm trying to build a generic controller mvc application. I have implemented the IControllerFactory but i get 404 when tryinng to access my custom page It doesn't even break when debugging on the createController method. It's like the route is going through the static files. I'm using dnx 1.0.0-rc1-update2 and mvc 1.0.0-rc1-final
Here's my startup:
``` c#
public class Startup
{
protected IApplicationEnvironment ApplicationEnvironment { get; set; }
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
ApplicationEnvironment = appEnv;
// Set up data directory
//string appRoot = appEnv.ApplicationBasePath;
//AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(appRoot, "App_Data"));
var appcontext = new WebApplicationContext() { ApplicationID = new Guid("{D5F3334D-E55B-46FB-BD11-A663D0249EA3}"), DbConnectionString= Configuration["Data:DefaultConnection:ConnectionString"], Environment=env.EnvironmentName };
appcontext.DbContextType = typeof(ModelContainer);
WebApplicationContext.Current = appcontext;
}
public ModelContainer CreateDbContext()
{
var connectionString = Configuration["Data:DefaultConnection:ConnectionString"];
return new ModelContainer(connectionString);
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging();
services.AddScoped(provider =>
{
return CreateDbContext();
});
// Add framework services.
services.AddSingleton<WebApplicationContext>(i =>
{
return WebApplicationContext.Current;
});
services.AddMvc();
services.AddSession();
services.AddSqlServerCache(options =>
{
options.ConnectionString = Configuration["Data:DefaultConnection:ConnectionString"];
options.SchemaName = "dbo";
options.TableName = "Sessions";
});
var controller_activator = services.Where(p => p.ServiceType == typeof(IControllerActivator)).First();
services.Remove(controller_activator);
services.AddSingleton<IControllerActivator, GenericControllerActivator>();
var controller_type_provider = services.Where(p => p.ServiceType == typeof(IControllerTypeProvider)).First();
services.Remove(controller_type_provider);
services.AddSingleton<IControllerTypeProvider, GenericControllerTypeProvider>();
var controller_factory_service = services.Where(p => p.ServiceType == typeof(IControllerFactory)).First();
services.Remove(controller_factory_service);
services.AddSingleton<IControllerFactory, FrameworkControllerFactory>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseSession();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
});
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
```
Can you help me?
Thanks!
@rynowak @sebastienros thoughts on how to implement something like this?
I don't know what you mean by 'generic controller mvc application' but I will assume that you want to load controllers dynamically or based on some configuration, where the route would match to specific controllers. In that case you can just use ApplicationPartManager to register these controllers.
In the Configure method you can resolve the service and use it, here is a sample that we use in Orchard.
var applicationPartManager = builder.ApplicationServices.GetRequiredService<ApplicationPartManager>();
applicationPartManager.ApplicationParts.Add(new AssemblyPart(extensionEntry.Assembly));
There are other way to add the controllers that the assembly itself. If this is not what you are trying to accomplish then you can find more examples in this class: https://github.com/aspnet/Mvc/blob/0d0aad41f501243f250b77613c015b4267e8c78d/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs
Where it defines the default way to compose applications from different _parts_ and _features_.
I'm trying to implement a feature with a generic controller such as GenericController
I assume when you say GenericController you actually mean GenericController<T>.
You can simply register your own services before calling AddMvc, this way MVC won't try to register its own implementations. MVC does it by using TryAdd methods on the DI, which won't register anything if one service is already.
c.f. https://github.com/aspnet/Mvc/blob/f638c051fa79c293ea7a0a859ed9700db9a18d71/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs#L158
I've seen a completely different approach recommended based on implementing an IApplicationFeatureProvider
Thanks brentarias! I had the same question as the user on stackoverflow!
Hi @xaviergxf, do you still have any open questions on this issue, or is everything resolved?
Hi Eilon,
I haven't tried yet these fixes. You may close for now the question.
Thanks
Xavier
On Apr 21, 2016 6:16 PM, "Eilon Lipton" [email protected] wrote:
Hi @xaviergxf https://github.com/xaviergxf, do you still have any open
questions on this issue, or is everything resolved?—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/aspnet/Mvc/issues/4474#issuecomment-212991474
Ok!
Hi Eilon,
I've done some tests and it still doesn't work quite right. The route is recognized but before the createController is called the action signature is verifed and it cannot find any signature because the signatures are only from the concrete controllers. How can i instantiate a generic controller
Thanks!
I'm looking into the mvc code on github and i managed to understand what's the source of the problem:
When the url is called http://localhost:8081/product/index and there's no productController but only GenericController
Thanks!
@xaviergxf can you show more of what you tried and where you're getting stuck?
Hi Eilon,
Here's what i have in my mvc core project:
The objective is to instantiate the corresponding controller or if this controller doesn't exist instantiate the GenericController
Example:
Before mvc core, the ControllerFactory was responsible for creating the controller and then the action was chosen.
Best regards
@rynowak what might block IControllerFactory.CreateController from being called? Does the Application Model need to already know about the controller/action in order to even get that far? Or maybe something else?
Pretty much everything in MVC Core assumes that that all actions and controllers are enumerated at startup. If you can't do this for some reason things are going to get much more complicated.
The simplest path forward for you will be to plug into this system. You can implement a custom IApplicationFeatureProvider<ControllerFeature> - (See ControllerFeatureProvider as an example) and plug that into the ApplicationPartManger, this will let you add additional types which are the closed-generic instantiations of your generic controller.
public class MyProvider : IApplicationFeatureProvider<ControllerFeature>
{
...
}
services.AddMvc().ConfigureApplicationPartManager(partManger =>
{
partManger.FeatureProviders.Add(new MyProvider());
});
I've managed to make it work with the following setup:
services.AddSingleton<IControllerFactory, GenericControllerFactory>();
services.AddTransient<IControllerActivator, GenericControllerActivator>();
services.AddTransient<IApplicationModelProvider, GenericApplicationModelProvider>();
services.AddMvc().ConfigureApplicationPartManager(s=> {
s.FeatureProviders.Insert(0, new GenericControllerFeatureProvider());
});
I needed IApplicationModelProvider because it provides the properties and methods of the controller.
Before mvc core the controller factory was invoked before determining the action to be called, this allowed things to be more dynamic. Can you please explain why the current architecture was designed the way it is?
Thanks!
@xaviergxf can you show the sample code? GenericControllerFactory,GenericControllerActivator,GenericApplicationModelProvider
Here's my implementation: https://bitbucket.org/xaviergxf/public/downloads/
@xaviergxf thanks
Here's a sample of what I had in mind: https://github.com/aspnet/Entropy/commit/42171b706540d23e0298c8f16a4b44a9ae805c0a
The difference here is that the IApplicationFeatureProvider<ControllerFeature> adds the closed-generic types as controller types. You also need to set the controller name, which I did with a convention (look at the attribute on GenericController<>.
Is this close to what you wanted? Let me know and I help.
This reuses all of the defaults for the application model and controller factory/activator so there's no need to duplicate any functionality.
Before mvc core the controller factory was invoked before determining the action to be called, this allowed things to be more dynamic. Can you please explain why the current architecture was designed the way it is?
The first issue that comes to mind is attribute routing - MVC 1-4 had no up-front knowledge of what controllers and actions existed in your application, and no real 'descriptor' for your controllers and actions. This just isn't compatible with attribute routing (added in MVC5), because you need to know all the actions and routes when serving the first request. So when we did add attribute routing, we were boxed into a few particular design choices, and worse, using attribute routing broke the kind of dynamism that controller factory long supported.
In addition, any component which needs to understand controllers and actions needs to come up with it's own understanding, since there's no shared representation of controllers and actions as _data_.
The second issue is that making things 'dynamic' tends towards the need to replace framework components that are almost always _global_ and sometimes very complex.
The problem with replacing a global component (like the controller factory) is that you can only have one version of it - so if you need to replace the controller factory to support generics, and replace the controller factory to support DI, then you can run into a conflict.
The problem with replacing a complex component is that well, people get it wrong. We've been guilty in the past of shipping some extensibility that's really hard to implement correctly. Mostly the problem people run into is performance, due to the concerns in my first point - "any component which needs to understand controllers and actions needs to come up with it's own understanding". I don't mean losing a millisecond or two off your response times, I've seen cases where a reflection call in the wrong place slowed a site down by 20x.
The third issue, that I hinted at above, is that some designs (like the old controller factory) are non-orthogonal. The controller factory in MVC5 is responsible for:
The common reasons to replace the controller factory listed above are sometimes 1 & 3 and often 4. Users often will break or reimplement number 2 while doing these things.
I'd describe the current design as having a separation between:
If we did the engineering right, you should mostly extend the framework by adding "things that provide data". Hopefully if you need to replace a "things that do something based on data" then you do so by implementing a single, simple component that just does different.
@xaviergxf did you get a chance to take a look at the sample here? aspnet/Entropy@42171b7
Hi Ryan,
Not yet.,..sorry. I'll look at it next week.
Thanks.
Xavier Eric Moura Klausener
On Fri, Jul 29, 2016 at 8:48 PM, Ryan Nowak [email protected]
wrote:
@xaviergxf https://github.com/xaviergxf did you get a chance to take a
look at the sample here? aspnet/Entropy@42171b7
https://github.com/aspnet/Entropy/commit/42171b7—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/aspnet/Mvc/issues/4474#issuecomment-236262666, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AJPxlJKxteo0IDMWMSszZEqQex2_5Psoks5qakrngaJpZM4IHO5K
.
Hi rynowak,
Your example is perfect! It works much better than mine. Mine had some action AmbiguousActionException's in login with Identity. Thanks a lot for updating the samples.
Xavier
Ok great, I'm going to create a PR for that sample then so that we can refer other people to it.
@rynowak @sebastienros : y'alls peoples have any thoughts on implementing IApplicationFeatureProvider<ControllerFeature> in a way that the implementation can receive an injected IOptions<SomeOptions>? I mean, besides implementing IActionDescriptorProvider and having Order return negative infinity...
My thought it that this is like an 11/10 difficulty. Options is a DI pattern. Feature providers have to be created before DI.
What is it that you're trying to accomplish?
The same boring GenericController<TEntity> type of setup that everyone else wants. I would like for it to be able to take advantage of the IOptions<>/IConfigureOptions<> infrastructure, though, and I would like to avoid manually constructing all of the ControllerModel/ActionModel/SelectorModel objects if possible (it would be nice to take advantage of the out-of-box conventions that set these up for the controller types.)
Maybe I can do something horrific like have my IApplicationModelProvider implementation receive an IServiceProvider and, at the right time, resolve IEnumerable<IApplicationModelProvider> and run all of them excluding itself. :scream:
It's possible you will run into a cycle. Feature providers can be called before the IServiceProvider is available.
Is the use of options for configuring the IApplicationFeatureProvider<ControllerFeature>?
Yes, it would be for the IApplicationFeatureProvider<ControllerFeature> to know which controller types to populate in the ControllerFeature.
Edit: to clarify my earlier comment, I meant using an IApplicationModelProvider instead of an IApplicationFeatureProvider<ControllerFeature> to do that horrific thing.
I wish you luck! Let us know what works and doesn't work
Hi rynowak,
How about integrating the generic controller with authorization attributes? Let's say for the generic controllers i want to only give access to Admin role only. How can i override this access and give access to another role after inheriting the generic controller?
Thanks!
@xaviergxf , once inherited you may place an Authorize attribute on the inherited class. However, you cant specify authrization on each single inherited action method. You need to verify authorization in the generic controller action methods "manually", while inherited controller might specify the required authorization policy tjrough an interface implementation, or by overriding some inerited methods.
@tuespetre . Adding, also options to automatic creation of controllers based on generic controllers is quite "too much" for a customization. Maybe, at this point, it is better to implement a different middleware from the scratch instead of using controllers. I mean a different type of middleware based on some "coherent theory", otherwise you risk to write spaghetti code instead of a "smart generalization". For instance why dont you use
OData libraries? They are based on data diagrams, ie, they have a clean and coherent framework you may use to define middleware behaviour.
Anyway, a simpler approach would be the use of DataAnnotations on ViewModels (The T of your GenericController
Something like a CreateControllerAttribute that accepts two types as arguments the generic controller type to use, and an interface implementation that specifies contoller peculiarities.
You may discover all DAtaAnnotation by using AssemblyParts, an then add the needed controllers.
We are closing this issue because no further action is planned for this issue. If you still have any issues or questions, please log a new issue with any additional details that you have.
Most helpful comment
Sample
Here's a sample of what I had in mind: https://github.com/aspnet/Entropy/commit/42171b706540d23e0298c8f16a4b44a9ae805c0a
The difference here is that the
IApplicationFeatureProvider<ControllerFeature>adds the closed-generic types as controller types. You also need to set the controller name, which I did with a convention (look at the attribute onGenericController<>.Is this close to what you wanted? Let me know and I help.
This reuses all of the defaults for the application model and controller factory/activator so there's no need to duplicate any functionality.
Design
The first issue that comes to mind is attribute routing - MVC 1-4 had no up-front knowledge of what controllers and actions existed in your application, and no real 'descriptor' for your controllers and actions. This just isn't compatible with attribute routing (added in MVC5), because you need to know all the actions and routes when serving the first request. So when we did add attribute routing, we were boxed into a few particular design choices, and worse, using attribute routing broke the kind of dynamism that controller factory long supported.
In addition, any component which needs to understand controllers and actions needs to come up with it's own understanding, since there's no shared representation of controllers and actions as _data_.
The second issue is that making things 'dynamic' tends towards the need to replace framework components that are almost always _global_ and sometimes very complex.
The problem with replacing a global component (like the controller factory) is that you can only have one version of it - so if you need to replace the controller factory to support generics, and replace the controller factory to support DI, then you can run into a conflict.
The problem with replacing a complex component is that well, people get it wrong. We've been guilty in the past of shipping some extensibility that's really hard to implement correctly. Mostly the problem people run into is performance, due to the concerns in my first point - "any component which needs to understand controllers and actions needs to come up with it's own understanding". I don't mean losing a millisecond or two off your response times, I've seen cases where a reflection call in the wrong place slowed a site down by 20x.
The third issue, that I hinted at above, is that some designs (like the old controller factory) are non-orthogonal. The controller factory in MVC5 is responsible for:
The common reasons to replace the controller factory listed above are sometimes 1 & 3 and often 4. Users often will break or reimplement number 2 while doing these things.
I'd describe the current design as having a separation between:
If we did the engineering right, you should mostly extend the framework by adding "things that provide data". Hopefully if you need to replace a "things that do something based on data" then you do so by implementing a single, simple component that just does different.