Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FAQ and .asmx support #24

Open
lscorcia opened this issue Mar 16, 2021 · 9 comments
Open

FAQ and .asmx support #24

lscorcia opened this issue Mar 16, 2021 · 9 comments

Comments

@lscorcia
Copy link

The FAQs mention that asmx support should work - I'd be happy to discover I'm wrong, but from my tests on this regard, it seems that they do not.

A valid workaround is mentioned at https://www.milestonetg.com/blog/development/2018/02/17/adding-new-microsoft-extensions-to-legacy-wcf-and-asmx-web-services.html , i.e. use constructor chaining with the service locator:

    [WebService(Namespace = "http://com.github/ws/myheadspins/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class MyHeadSpinsWsAdapter : System.Web.Services.WebService
    {
        private ILogger log { get; }
        private MyBusinessService srvBusiness { get; }

        public MyHeadSpinsWsAdapter(): this(
            (ILoggerFactory)HttpRuntime.WebObjectActivator?.GetService(typeof(ILoggerFactory)),
            (MyBusinessService)HttpRuntime.WebObjectActivator?.GetService(typeof(MyBusinessService)))
        {

        }

        public MyHeadSpinsWsAdapter(ILoggerFactory loggerFactory, MyBusinessService srvBusiness)
        {
            log = loggerFactory.CreateLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            this.srvBusiness = srvBusiness;
        }
...
@nathanrobb
Copy link

We received this exception when running an asmx WebService with constructor injection.

System.MissingMethodException: No parameterless constructor defined for this object.
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at System.Web.Services.Protocols.ServerProtocol.CreateServerInstance()
   at System.Web.Services.Protocols.WebServiceHandler.Invoke()
   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()

We also switched to using constructor chaining and manually injecting the services to get this going.

@daiplusplus
Copy link
Owner

daiplusplus commented Apr 9, 2021

@nathanrobb Can you share your project or a minimal reproduction? This is something I'd like to fix but I can't reproduce it. I believe it's doable by fiddling with the BuildProvider system in ASP.NET.

@nathanrobb
Copy link

nathanrobb commented Apr 9, 2021

A minimal reproduction can be found at https://github.com/nathanrobb/AspNetDependencyInjectionWebServiceExample

Adding this project nathanrobb/AspNetDependencyInjectionWebServiceExample@e33a532

This repo has a working WebForm page with constructor DI reachabe at:
https://github.com/nathanrobb/AspNetDependencyInjectionWebServiceExample/blob/main/Example/TestPage.aspx.cs
http://localhost:56145/TestPage.aspx

And a currently working WebService with a default constructor workaround at:
https://github.com/nathanrobb/AspNetDependencyInjectionWebServiceExample/blob/main/Example/Test.asmx.cs
http://localhost:56145/Test.asmx?op=HelloWorld

Removing the default constructor from the Test.asmx.cs class will cause the above request to fail with the MissingMethodException.

Hope this helps. Thanks.

@TheJayMann
Copy link
Collaborator

This most likely is not an issue with the AspNetDependencyInjection library, but with ASP.NET itself. Reading another article mentioning how to integrate Unity with WebForms dependency injection, near the end of the article it mentions where dependency injection can be used. In this list, WebService class is not mentioned, and WebService does not derive from or implement any of the mentioned items in the list.

Internally, this works by ASP.NET calling the internal method of either CreateNonPublicInstanceByWebObjectActivator or CreatePublicInstanceByWebObjectActivator. This will then use the WebObjectActivator to create the instance if one is available, or fall back to the CreateNonPublicInstance or CreatePublicInstance method if one is not available. My guess is that ASP.NET does not make use of these methods when instantiating a WebService. If this is the case, it would not be possible to use this project, or really any other project which adds dependency injection to ASP.NET by using WebObjectActivator, to integrate dependency injection into web services. Thus, the only way to get dependencies into a web service would be to use the service locator pattern, which appears to be the workaround you chose to use.

@watfordgnf
Copy link

It looks like WebService objects are created via Activator.CreateInstance:

        internal virtual void CreateServerInstance() {
            target = Activator.CreateInstance(ServerType.Type);
            WebService service = target as WebService;
            if (service != null)
                service.SetContext(context);
        }

https://referencesource.microsoft.com/#System.Web.Services/System/Web/Services/Protocols/ServerProtocol.cs,54

@lscorcia
Copy link
Author

Thanks to everyone that contributed to the quest! I don't think there will ever be a more elegant way than constructor chaining, the reason I opened this issue was just to highlight the need to update the paragraph at https://github.com/Jehoel/AspNetDependencyInjection/blob/master/GETTING_STARTED.md#can-i-use-constructor-dependency-injection-with-asmx-files , where it says it's untested but should work. Unfortunately, it does not (but this library is awesome anyway!).

@daiplusplus
Copy link
Owner

daiplusplus commented Jul 19, 2021

I've got an implementation of this almost done and working.

How it works is you'll need to add a <system.webServer><handlers> (or <system.web><httpHandlers> if you're still running IIS6 in 2021) which uses a custom subclass of ScriptHandlerFactory (which will be included in this project, eventually) which does some IL Emit jiggerypokery to work-around the fact that ServerProtocol's CreateServerInstance method is internal virtual - which then allows us to return an instance of WebService created using MEDI/ANDI instead of the immediate call to Activator.CreateInstance.

Gimmie a few more days to polish it off. My prototype implementation does look like it will be able to fully support async and non-async, and IRequiresSession and non-session WebService handlers.

@daiplusplus
Copy link
Owner

daiplusplus commented Aug 7, 2021

My proof-of-concept is almost ready for a beta release for this (for support for ASMX files).

One last problem is the System.Web.Services.Protocols.ServerProtocol.CreateServerInstance() method - this method's hardcoded reference to Activator.CreateInstance(Type) is the reason why .asmx files and WebService types can't get constructor dependency injection.

The method is internal virtual so an external project can't subclass the ServerProtocol type and then override CreateServerInstance. I tried during it with a TypeBuilder but that failed at runtime as apparently the program-loader verifies internal access barriers are checked - huh! (I honestly thought access-modifiers didn't matter to it - and full-trust IL code could do anything...)

I've resorted to trying out actual machine-code modification and replacement to replace the contents of ServerProtocol.CreateServerInstance but I keep getting AccessViolationExceptions from the runtime whenever the program writes back to a function-pointer - no-one else on the Internet seems to be having this issue - which doesn't help...

@daiplusplus
Copy link
Owner

I decided to try using HarmonyLib and that seems to work - but I don't like the idea of adding it as a dependency to the main package, so I'll put ASMX support in a separate package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants