I got all my code copied in to a new solution and spent hours tirelessly "fixing" all the breaking change differences to be faced with this ...
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3] Route matched with {action = "Get", controller = "App"}.
Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Get(Microsoft.AspNet.OData.Query.ODataQueryOptions`1[Core.Objects.Entities.CMS.App]) on controller Api.Controllers.AppController (Api).
Process is terminating due to StackOverflowException.
.. The code didn't previously exhibit this behaviour of course and putting a breakpoint in the action does nothing as it isn't getting that far which makes me think it's getting stuck in a loop in the DI container somehow.
Any ideas how to debug this?
Further information ...
Startup.cs
public class Startup
{
static readonly ILog log = LogManager.GetLogger(typeof(Startup));
static Config Config;
public Startup(IConfiguration configuration)
{
Config = new Config();
configuration.Bind(Config);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddAuthInfo();
services.AddSingleton(Config);
services.AddResponseCompression();
services.AddMvcCore(options =>
{
options.EnableEndpointRouting = false; // TODO: Remove when OData does not causes exceptions anymore
})
.AddAuthorization()
.AddDataAnnotations()
.AddJsonFormatters(options => options.ReferenceLoopHandling = ReferenceLoopHandling.Ignore)
.AddCors()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddOData();
services.AddDbContext<MembershipDataContext>(options => options.UseSqlServer(Config.Connections["Membership"]));
services.AddDbContext<CoreDataContext>(options => options.UseSqlServer(Config.Connections["Core"]));
services.AddDbContext<B2BDataContext>(options => options.UseSqlServer(Config.Connections["B2B"]));
services.AddScoped<IMembershipDataContext, MembershipDataContext>();
services.AddScoped<ICoreDataContext, CoreDataContext>();
services.AddScoped<IB2BDataContext, B2BDataContext>();
services.AddServices();
services.AddSingleton(typeof(ICrypto<Signature>), new AesCrypto<Signature>());
var installers = TypeHelper.GetWebStackAssemblies()
.SelectMany(a => a.GetTypes()
.Where(t => !t.IsAbstract && t.GetInterfaces().Any(i => i == typeof(IPackageInstaller))))
.ToArray();
var importers = TypeHelper.GetWebStackAssemblies()
.SelectMany(a => a.GetTypes()
.Where(t => !t.IsAbstract && t.GetInterfaces().Any(i => i == typeof(IPackageItemImporter))))
.ToArray();
installers.ForEach(i => services.AddScoped(typeof(IPackageInstaller), i));
importers.ForEach(i => services.AddScoped(typeof(IPackageItemImporter), i));
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
log.Info("Initialising ...");
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.MapWhen(
context => Regex.IsMatch(context.Request.Path.ToString(), "^App({[0-9]:[0-9]})/DMS/.*"),
appBranch => appBranch.UseMiddleware<DMSMiddleware>()
);
log.Info("Databases");
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
new DbContext[] {
serviceScope.ServiceProvider.GetRequiredService<MembershipDataContext>(),
serviceScope.ServiceProvider.GetRequiredService<CoreDataContext>(),
serviceScope.ServiceProvider.GetRequiredService<B2BDataContext>()
}.ForEach(ctx => ctx.Database.Migrate());
}
log.Info("WebApi & Odata");
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.MapRoute("default", "{controller=Home}/{action=Get}");
});
foreach (var builder in new ODataModelBuilder[] { new MembersModelBuilder(), new CoreModelBuilder(), new B2BModelBuilder(), new SearchModelBuilder() })
{
var model = builder.Build();
app.UseOData(model.Context + "Api", builder.GetType().Name.Replace("ModelBuilder", ""), model.EDMModel);
}
log.Info("Starting SignalR");
app.UseSignalR(routeBuilder => {
routeBuilder.MapHub<NotificationHub>("/Hub/Core");
routeBuilder.MapHub<NotificationHub>("/Hub/Notification");
routeBuilder.MapHub<NotificationHub>("/Hub/Workflow");
});
log.Info("Initialisation Complete");
}
}
.. the app hosts a few misc controllers (standard webapi) 4 OData contexts and a set of SignalR hubs.
There's no front end for this it's purely API.
I also added this extension which is used for IServiceCollection ...
static Type[] serviceTypes = TypeHelper.GetWebStackAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.ImplementsGenericInterface(typeof(IService<,,>)))
.Where(t => !t.IsAbstract)
.ToArray();
public static void AddServices(this IServiceCollection services)
{
foreach (var tCtx in TypeHelper.GetContextTypes())
{
var entityTypes = TypeHelper.GetEntityTypesFor(tCtx);
var tUser = tCtx.GetInterfaces().First(i => i.Name == "IDataContext`1").GenericTypeArguments[0];
var contextInterfaceBase = TypeHelper.GetWebStackAssemblies()
.SelectMany(a => a.GetTypes())
.First(t => t.IsInterface && t.Name.StartsWith($"I{tCtx.Name.Replace("DataContext", "")}Service"));
entityTypes.ForEach(entity =>
{
var types = new[] { entity, entity.GetIdProperty().PropertyType };
var result = serviceTypes
.Where(t => t.Name == entity.Name + "Service")
.FirstOrDefault(t => t.GetInterfaces().FirstOrDefault(ii => ii.Name.StartsWith($"I{tCtx.Name.Replace("DataContext", "")}Service")) != null);
if (result == null)
result = serviceTypes.First(t => t.Name.StartsWith($"{tCtx.Name.Replace("DataContext", "")}Service")).MakeGenericType(types);
// 2. For most specific implemntation extract interfaces in the stacked naming form
var interfaces = result.GetInterfaces()
.Where(ii => ii.Name.StartsWith($"I{tCtx.Name.Replace("DataContext", "")}Service") || ii.Name == $"I{entity.Name}Service")
.ToArray();
// 3. For each interface add binding
interfaces.ForEach(i => services.AddScoped(i, result));
});
}
}
The idea here is to pick the "most concrete implementation" of an interface stack that looks something like ...
IService<T, TKey, TUser>
IContextService<T, TKey> : IService<T, TKey, ContextUser>
IFooService : IContextService<Foo, int>
... it then is supposed to register the most concrete implementation against all of the found interfaces that are relevant generically .. this is essentially a re-implementation of Ninject's convention based bindings that look something like this ...
var kernel = new StandardKernel();
kernel.Bind(x => x.FromAssembliesMatching("Core.*")
.SelectAllClasses()
.BindAllInterfaces()
);
... Is it possible that this code would result in some weird circular referencing issue?
I can load the application and do a simple get request on the RootControllers default Get method but as soon as I hit the OData controllers (any of them) I get a stack overflow and the app crashes giving the exception details in the VS output window.
I have this issue whatever OData controller I hit but as an example my AppController found at "~/Core/App" is contructed like this ...
public AppController(IAppService service, IFolderService dms, IFileService files, IAuthInfo auth) : base(service, auth) { Dms = dms; Files = files; }
... If I follow the dependency graph down by looking through the code I don't see anything like say an IFooService that requires an IBarService which in turn Requires an IFooService nor are there any dependencies that do that in larger circles.
I had considered an issue in the DbContext but that should at least allow me to put a breakpoint in the controller and work my down to the issue with the debugger surely?
Aucun commentaire:
Enregistrer un commentaire