spring-projects / spring-net

Spring Framework for .NET
http://www.springframework.net
Apache License 2.0
847 stars 375 forks source link

Changes in Spring.Core required for Asp.Net Mvc Core #189

Open robsosno opened 5 years ago

robsosno commented 5 years ago

I've tried to use Spring.Net together with Asp.Net Mvc . I've found that Asp.Net MVC tries to register open generics in the container which is not supported. Few examples of such types: Microsoft.Extensions.Logging.ILogger<>, IOptions<>, IOptionsFactory<>. This is important because big part if not majority of .NET applications are Asp.Net MVC applications these days. Spring.NET is among few IoC frameworks which doesn't support open generics. Unfortunately adding this to Spring.Core is far beyond my skills.

mashbrno commented 4 years ago

@lahma Maybe you have an idea which should be the direction of Spring.NET for ASP.NET core. There are multiple attitudes so far - replacing built-in IoC or just extending it.

So far I've failed with writing any IServiceProvider mostly because of typed fashion of Core IoC. Also three scopes were difficult - Transient, Scoped, Singleton.

So my current workarounds are two:

  1. Controllers are created by standard IoC, some parameters injected via constructor and Spring ones assigned via ServiceLocator.
  2. Inside Startup.ConfigureServices() are controllers registered with lambdas, using ServiceLocator again.
maulik-modi commented 3 years ago

@mashbrno , would you mind sharing your prototype?

mashbrno commented 3 years ago

Finally I used a very simplistic approach which suffice my needs.

There is an Activator

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Spring.Context;
using Spring.Context.Support;

namespace Spring.AspNetCore
{
    public class SpringControllerActivator : IControllerActivator
    {
        private IApplicationContext _ctx;

        public object Create(ControllerContext actionContext)
        {
            var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();
            return Context.GetObject(controllerType.Name);
        }

        public virtual void Release(ControllerContext context, object controller)
        {
        }

        private IApplicationContext Context
        {
            get
            {
                if (_ctx == null)
                    _ctx = ContextRegistry.GetContext();
                return _ctx;
            }
        }
    }
}

registred in Startup.ConfigureServices() like services.AddSingleton<IControllerActivator, SpringControllerActivator>();

and then two Service Locators to share object created by the built-in IoC and vice-versa. Globally stored variable IApplicationBuilder.ApplicationServices fails probably when working with scopes, but to generate URLs and action links within Spring controllers works fine.

When a view needs to access a service defined by Spring the other ServiceLocator returning Spring context objects is used.

maulik-modi commented 3 years ago

@mashbrno , When you say, service locator, You mean using

IServiceProvider OR Spring.Objects.Factory.IObjectFactory (http://www.springframework.net/doc-1.1-M1/sdk/2.0/html/Spring.Core~Spring.Objects.Factory.IObjectFactory~GetObject.html)

constructor(IServiceProvider services)
{ 
  this.services = services;
}
private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#consuming-a-scoped-service-in-a-background-task

mashbrno commented 3 years ago

By service locator I mean the pattern.

In MVC views, where I need a Spring defined service I use: ContextRegistry.GetContext()[<name>] and within controllers where I need an object defined by Microsoft IoC:


public class Startup 
{
    public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ServiceProvider = app.ApplicationServices;
        ...
    }

    public static IServiceProvider ServiceProvider { get; private set; }
}
maulik-modi commented 3 years ago

@mashbrno , Thanks for your response, I still cannot figure out how you build the Spring container as it only supports XML based configuration on the other hand ASP.NET DI uses code based approach.

Does this still work?

IApplicationContext ctx =return new XmlApplicationContext("application-context.xml");

mashbrno commented 3 years ago

Yes (I assume the return keyword is a typo), you can create the context in the very same way as for any console app. I personally use Spring.FluentContext which has many benefits (checks the syntax during build, syntax highliting), but also drawbacks as lambas require properties to have public getters.

maulik-modi commented 3 years ago

@mashbrno , Have you tried running this cross platform? Our main motivation moving from spring to asp.net core was cross platform.

mashbrno commented 3 years ago

If you mean containers then yes, I run this on both Windows and Kubernetes. As moving to ASP.NET Core is challenging itself, you should also consider to swap IoC as well. Autofac or SimpleInjector leverage tons of new features since .NET 3.5 like generics, lambdas, scopes. They're also proven to be faster.

The reason why I stick with Spring.NET is the possibility to create deffered child application contexts. Superb Spring's Expression and Validation frameworks I believe can be used independently with any IoC.

maulik-modi commented 3 years ago

@mashbrno , What version of Spring.net are you using on Linux - source or Nuget link of spring.net please? We tried to build from source but faced compilation issues and lot of tests failing.

mashbrno commented 3 years ago

I use my own set which are built from the public source. Just lahma was not producing any .NET Standard packages 2 years ago. Finally he manage to access the official repo, so they exist again.

lahma commented 3 years ago

Adding my answer w.r.t. Linux usage here too.

Tests are run on both Windows and Linux. So the library should work on both platforms, but there might be some differences not spotted yet as 3.0 brought the cross-platform support.

Didn't know (or remember) there's Spring.FluentContext, ideally it would use the new 3.0 release if there's nothing blocking to do so anymore.

maulik-modi commented 3 years ago

@mashbrno , Microsoft recommends not to capture ServiceProvider as Static property https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines Avoid static access to services. For example, avoid capturing IApplicationBuilder.ApplicationServices as a static field or property for use elsewhere.

mashbrno commented 3 years ago

@maulik-modi I completely agree and I mentioned my concerns in previous comments. But as my case there are very few objects and I use it to generate action links only it just works. The rest is handled by Spring.

maulik-modi commented 3 years ago

I checked Microsoft implementation of Dependency Injection abstractions image

@lahma, Can you please confirm if this is true as raised by @robsosno ? . Spring.NET is among few IoC frameworks which doesn't support open generics. Unfortunately adding this to Spring.Core is far beyond my skills.

lahma commented 3 years ago

Can you please confirm if this is true as raised by @robsosno ? . Spring.NET is among few IoC frameworks which doesn't support open generics. Unfortunately adding this to Spring.Core is far beyond my skills.

I believe this is the state of matters.