在运行时创建WCF服务

发布于 2024-12-27 04:26:35 字数 522 浏览 0 评论 0原文

我们将根据运行时读取的元数据构建一个 Web 服务。我指的是整个网络服务:签名、合同和实施。

从这里我看到有两条主要路径。

第一条路径是生成代码。您可以在字符串中生成 C# 代码并即时编译它,或者更优雅(且复杂)地发出 MSIL 代码。这样您就拥有了 WCF 代码,并且 WCF 将负责从它生成 WSDL。

第二条途径是使用通用服务。具有接受所有内容的操作消息进程(消息)的服务。我们仍然希望将服务公开为“正常”服务,因此我需要在某个地方使用 WSDL。如何创建 WSDL?我考虑过使用 System.ServiceModel.Description,直到我意识到在内心深处,这个 API 依赖于具体类型。通过这种方法,我们不会有任何数据协定类型,并且可以动态处理 XML,使用元数据来解释它。因此我们需要以某种方式生成 WSDL。这是一个疯狂的想法吗? WSDL 有相当复杂的规范...

第三种选择是使用混合方法,发出类型只是为了创建签名,但使用非发出的代码(反映发出的类型)来实现服务。很奇怪,但可能比手工制作 WSDL 更简单...

有建议吗?

We are going to build a web service from metadata read at runtime. I mean the entire web service: the signatures, contracts and implementation.

There are two main paths I see from here.

The first path is that you generate code. Either you generate C# code in strings and compile it on the fly or more elegantly (and complicatedly), you emit MSIL code. This way you have WCF code and WCF will take care of generating the WSDL from it.

The second path is to use a generic service. A service with an operation Message Process(Message) accepting everything. We still want to expose the service as a 'normal' service, so I would need a WSDL somewhere. How can I create a WSDL? I thought about using System.ServiceModel.Description until I realised that deep inside, this API depends on concrete types. With this approach we wouldn't have any data contract type and would process XML on the fly, using metadata to interpret it. So we need somehow to generate the WSDL. Is that a crazy idea? WSDL has quite a complicated spec...

A third option would be to use an hybrid approach, emitting types just to create signatures but implementing the service using non-emitted code (reflecting on emitted types). Weird, but might be simpler than hand crafting WSDL by hand...

Suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

乖不如嘢 2025-01-03 04:26:35

做起来很痛苦,但对于发射类型来说是可能的。创建您的 .svc 并将其指向您的自定义 ServiceHostFactory:

<%@ ServiceHost Language="C#" Debug="true" Factory="Foo.FooServiceHostFactory" %>

[ServiceContract]
public class FooService { }

您的 ServiceHostFactory 是您生成操作合同、随之而来的类型等的地方:

public class FooServiceHostFactory : ServiceHostFactory {
public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses) {
    ServiceHost serviceHost = new FooServiceHost(baseAddresses);
    serviceHost.AddDefaultEndpoints();
    GenerateServiceOperations(serviceHost);
    return serviceHost;
}

private void GenerateServiceOperations(ServiceHost serviceHost) {
    var methodNames = new[] {
        new { Name = "Add" },
        new { Name = "Subtract" },
        new { Name = "Multiply" }
    };

    foreach (var method in methodNames) {
        foreach (var endpoint in serviceHost.Description.Endpoints) {
            var contract = endpoint.Contract;
            var operationDescription = new OperationDescription("Operation" + method.Name, contract);
            var requestMessageDescription = new MessageDescription(string.Format("{0}{1}/Operation{2}", contract.Namespace, contract.Name, method.Name), MessageDirection.Input);
            var responseMessageDescription = new MessageDescription(string.Format("{0}{1}/Operation{2}Response", contract.Namespace, contract.Name, method.Name), MessageDirection.Output);

            var elements = new List<FooDataItem>();
            elements.Add(new FooDataItem { Name = "X", DataType = typeof(int) });
            elements.Add(new FooDataItem { Name = "Y", DataType = typeof(int) });

            //note: for a complex type it gets more complicated, but the same idea using reflection during invoke()
            //object type = TypeFactory.CreateType(method.Name, elements);
            //var arrayOfType = Array.CreateInstance(type.GetType(), 0);

            //var parameter = new MessagePartDescription(method.Name + "Operation", contract.Namespace);
            //parameter.Type = arrayOfType.GetType();
            //parameter.Index = 0;
            //requestMessageDescription.Body.Parts.Add(parameter);

            var retVal = new MessagePartDescription("Result", contract.Namespace);
            retVal.Type = typeof(int);
            responseMessageDescription.Body.ReturnValue = retVal;

            int indexer = 0;
            foreach (var element in elements) {
                var parameter = new MessagePartDescription(element.Name, contract.Namespace);
                parameter.Type = element.DataType;
                parameter.Index = indexer++;
                requestMessageDescription.Body.Parts.Add(parameter);
            }

            operationDescription.Messages.Add(requestMessageDescription);
            operationDescription.Messages.Add(responseMessageDescription);
            operationDescription.Behaviors.Add(new DataContractSerializerOperationBehavior(operationDescription));
            operationDescription.Behaviors.Add(new FooOperationImplementation());
            contract.Operations.Add(operationDescription);
        }
    }
}

protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) {
    return base.CreateServiceHost(serviceType, baseAddresses);
}

}
在 ServiceHostFactory 中,您定义行为以及元数据,因此您的行为需要实现 IOperationBehavior 和 IOperationInvoker (或者您可以单独实现它们),并且看起来像这样:

public class FooOperationImplementation : IOperationBehavior, IOperationInvoker {
OperationDescription operationDescription;
DispatchOperation dispatchOperation;

public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {

}

public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) {

}

public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) {
    this.operationDescription = operationDescription;
    this.dispatchOperation = dispatchOperation;

    dispatchOperation.Invoker = this;
}

public void Validate(OperationDescription operationDescription) {

}

public object[] AllocateInputs() {
    return new object[2];
}

public object Invoke(object instance, object[] inputs, out object[] outputs) {
    //this would ALL be dynamic as well depending on how you are creating your service
    //for example, you could keep metadata in the database and then look it up, etc
    outputs = new object[0];

    switch (operationDescription.Name) {
        case "OperationAdd":
            return (int)inputs[0] + (int)inputs[1];
        case "OperationSubtract":
            return (int)inputs[0] - (int)inputs[1];
        case "OperationMultiply":
            return (int)inputs[0] * (int)inputs[1];
        default:
            throw new NotSupportedException("wtf");
    }
}

public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) {
    throw new NotImplementedException("Method is not asynchronous.");
}

public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) {
    throw new NotImplementedException("Method is not asynchronous.");
}

public bool IsSynchronous {
    get { return true; }
}

对于

复杂类型,这是您需要做出判断的地方调用是您问题的根源,是如何发出类型。这是一个示例,但您可以按照您认为合适的任何方式进行操作。我在我的 ServiceHostFactory 中调用它(警告:它是演示代码)

static public class TypeFactory {
    static object _lock = new object();
    static AssemblyName assemblyName;
    static AssemblyBuilder assemblyBuilder;
    static ModuleBuilder module;

    static TypeFactory() {
        lock (_lock) {
            assemblyName = new AssemblyName();
            assemblyName.Name = "FooBarAssembly";
            assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            module = assemblyBuilder.DefineDynamicModule("FooBarModule");
        }
    }

    static public object CreateType(string typeName, List<FooDataItem> elements) {
        TypeBuilder typeBuilder = module.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class);

        foreach(var element in elements) {
            string propertyName = element.Name;
            Type dataType = element.DataType;

            FieldBuilder field = typeBuilder.DefineField("_" + propertyName, dataType, FieldAttributes.Private);
            PropertyBuilder property =
                typeBuilder.DefineProperty(propertyName,
                                    PropertyAttributes.None,
                                    dataType,
                                    new Type[] { dataType });

            MethodAttributes GetSetAttr =
                    MethodAttributes.Public |
                    MethodAttributes.HideBySig;

            MethodBuilder currGetPropMthdBldr =
                typeBuilder.DefineMethod("get_value",
                                            GetSetAttr,
                                            dataType,
                                            Type.EmptyTypes);

            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit(OpCodes.Ldarg_0);
            currGetIL.Emit(OpCodes.Ldfld, field);
            currGetIL.Emit(OpCodes.Ret);

            MethodBuilder currSetPropMthdBldr =
                typeBuilder.DefineMethod("set_value",
                                            GetSetAttr,
                                            null,
                                            new Type[] { dataType });

            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit(OpCodes.Ldarg_0);
            currSetIL.Emit(OpCodes.Ldarg_1);
            currSetIL.Emit(OpCodes.Stfld, field);
            currSetIL.Emit(OpCodes.Ret);

            property.SetGetMethod(currGetPropMthdBldr);
            property.SetSetMethod(currSetPropMthdBldr);
        }

        Type generetedType = typeBuilder.CreateType();
        return Activator.CreateInstance(generetedType);
    }
}

更新:我编写了一个快速示例,它可用 这里

It's a pain to do, but possible to emitting types. Create your .svc and point it at your custom ServiceHostFactory:

<%@ ServiceHost Language="C#" Debug="true" Factory="Foo.FooServiceHostFactory" %>

[ServiceContract]
public class FooService { }

Your ServiceHostFactory is where you generate your operation contracts, types that go with it, etc:

public class FooServiceHostFactory : ServiceHostFactory {
public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses) {
    ServiceHost serviceHost = new FooServiceHost(baseAddresses);
    serviceHost.AddDefaultEndpoints();
    GenerateServiceOperations(serviceHost);
    return serviceHost;
}

private void GenerateServiceOperations(ServiceHost serviceHost) {
    var methodNames = new[] {
        new { Name = "Add" },
        new { Name = "Subtract" },
        new { Name = "Multiply" }
    };

    foreach (var method in methodNames) {
        foreach (var endpoint in serviceHost.Description.Endpoints) {
            var contract = endpoint.Contract;
            var operationDescription = new OperationDescription("Operation" + method.Name, contract);
            var requestMessageDescription = new MessageDescription(string.Format("{0}{1}/Operation{2}", contract.Namespace, contract.Name, method.Name), MessageDirection.Input);
            var responseMessageDescription = new MessageDescription(string.Format("{0}{1}/Operation{2}Response", contract.Namespace, contract.Name, method.Name), MessageDirection.Output);

            var elements = new List<FooDataItem>();
            elements.Add(new FooDataItem { Name = "X", DataType = typeof(int) });
            elements.Add(new FooDataItem { Name = "Y", DataType = typeof(int) });

            //note: for a complex type it gets more complicated, but the same idea using reflection during invoke()
            //object type = TypeFactory.CreateType(method.Name, elements);
            //var arrayOfType = Array.CreateInstance(type.GetType(), 0);

            //var parameter = new MessagePartDescription(method.Name + "Operation", contract.Namespace);
            //parameter.Type = arrayOfType.GetType();
            //parameter.Index = 0;
            //requestMessageDescription.Body.Parts.Add(parameter);

            var retVal = new MessagePartDescription("Result", contract.Namespace);
            retVal.Type = typeof(int);
            responseMessageDescription.Body.ReturnValue = retVal;

            int indexer = 0;
            foreach (var element in elements) {
                var parameter = new MessagePartDescription(element.Name, contract.Namespace);
                parameter.Type = element.DataType;
                parameter.Index = indexer++;
                requestMessageDescription.Body.Parts.Add(parameter);
            }

            operationDescription.Messages.Add(requestMessageDescription);
            operationDescription.Messages.Add(responseMessageDescription);
            operationDescription.Behaviors.Add(new DataContractSerializerOperationBehavior(operationDescription));
            operationDescription.Behaviors.Add(new FooOperationImplementation());
            contract.Operations.Add(operationDescription);
        }
    }
}

protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) {
    return base.CreateServiceHost(serviceType, baseAddresses);
}

}
In the ServiceHostFactory you define the behaviors along with the metadata, so your behavior would need to implement IOperationBehavior and IOperationInvoker (or you could implement them separately) and look something like this:

public class FooOperationImplementation : IOperationBehavior, IOperationInvoker {
OperationDescription operationDescription;
DispatchOperation dispatchOperation;

public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {

}

public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) {

}

public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) {
    this.operationDescription = operationDescription;
    this.dispatchOperation = dispatchOperation;

    dispatchOperation.Invoker = this;
}

public void Validate(OperationDescription operationDescription) {

}

public object[] AllocateInputs() {
    return new object[2];
}

public object Invoke(object instance, object[] inputs, out object[] outputs) {
    //this would ALL be dynamic as well depending on how you are creating your service
    //for example, you could keep metadata in the database and then look it up, etc
    outputs = new object[0];

    switch (operationDescription.Name) {
        case "OperationAdd":
            return (int)inputs[0] + (int)inputs[1];
        case "OperationSubtract":
            return (int)inputs[0] - (int)inputs[1];
        case "OperationMultiply":
            return (int)inputs[0] * (int)inputs[1];
        default:
            throw new NotSupportedException("wtf");
    }
}

public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) {
    throw new NotImplementedException("Method is not asynchronous.");
}

public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) {
    throw new NotImplementedException("Method is not asynchronous.");
}

public bool IsSynchronous {
    get { return true; }
}

}

For complex types, this is where you need to make a judgement call, which is the root of your question, is how to emit the types. Here's an example, but you can do it any way you can see fit. I'm calling this in my ServiceHostFactory (warning: it's demo code)

static public class TypeFactory {
    static object _lock = new object();
    static AssemblyName assemblyName;
    static AssemblyBuilder assemblyBuilder;
    static ModuleBuilder module;

    static TypeFactory() {
        lock (_lock) {
            assemblyName = new AssemblyName();
            assemblyName.Name = "FooBarAssembly";
            assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            module = assemblyBuilder.DefineDynamicModule("FooBarModule");
        }
    }

    static public object CreateType(string typeName, List<FooDataItem> elements) {
        TypeBuilder typeBuilder = module.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class);

        foreach(var element in elements) {
            string propertyName = element.Name;
            Type dataType = element.DataType;

            FieldBuilder field = typeBuilder.DefineField("_" + propertyName, dataType, FieldAttributes.Private);
            PropertyBuilder property =
                typeBuilder.DefineProperty(propertyName,
                                    PropertyAttributes.None,
                                    dataType,
                                    new Type[] { dataType });

            MethodAttributes GetSetAttr =
                    MethodAttributes.Public |
                    MethodAttributes.HideBySig;

            MethodBuilder currGetPropMthdBldr =
                typeBuilder.DefineMethod("get_value",
                                            GetSetAttr,
                                            dataType,
                                            Type.EmptyTypes);

            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit(OpCodes.Ldarg_0);
            currGetIL.Emit(OpCodes.Ldfld, field);
            currGetIL.Emit(OpCodes.Ret);

            MethodBuilder currSetPropMthdBldr =
                typeBuilder.DefineMethod("set_value",
                                            GetSetAttr,
                                            null,
                                            new Type[] { dataType });

            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit(OpCodes.Ldarg_0);
            currSetIL.Emit(OpCodes.Ldarg_1);
            currSetIL.Emit(OpCodes.Stfld, field);
            currSetIL.Emit(OpCodes.Ret);

            property.SetGetMethod(currGetPropMthdBldr);
            property.SetSetMethod(currSetPropMthdBldr);
        }

        Type generetedType = typeBuilder.CreateType();
        return Activator.CreateInstance(generetedType);
    }
}

update: I wrote a quick example and it is available here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文