Создание службы WCF во время выполнения - PullRequest
2 голосов
/ 14 января 2012

Мы собираемся создать веб-сервис из метаданных, читаемых во время выполнения. Я имею в виду весь веб-сервис: подписи, контракты и внедрение.

Здесь я вижу два основных пути.

Первый путь - создание кода. Либо вы генерируете код C # в строках и компилируете его на лету, либо, более элегантно (и сложно), вы генерируете код MSIL. Таким образом, у вас есть код WCF, и WCF позаботится о создании из него WSDL.

Второй путь - использовать универсальный сервис. Служба с операцией Message Process (Message), принимающей все. Мы все еще хотим представить сервис как «нормальный» сервис, поэтому мне понадобится где-нибудь WSDL. Как я могу создать WSDL? Я думал об использовании System.ServiceModel.Description, пока не понял, что глубоко внутри этот API зависит от конкретных типов. При таком подходе у нас не будет никакого типа контракта данных, и мы будем обрабатывать XML на лету, используя метаданные для его интерпретации. Поэтому нам нужно как-то сгенерировать WSDL. Это сумасшедшая идея? У WSDL довольно сложная спецификация ...

Третий вариант - использовать гибридный подход, испускающий типы только для создания подписей, но реализующий службу с использованием неизлучаемого кода (отражающего излучаемые типы). Странно, но может быть проще, чем создавать вручную WSDL ...

Предложения

1 Ответ

4 голосов
/ 02 мая 2012

Это боль, но возможна для излучающих типов.Создайте свой .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);
    }
}

обновление: я написал быстрый пример, и он доступен здесь .

...