Разработка и реализация плагина C # WCF - PullRequest
7 голосов
/ 27 апреля 2011

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

У меня будет веб-приложение MVC 3, которое действительно используется только для конфигурации, и библиотека классов (ядро), которая будет загружать различные плагины.

Буду признателен за некоторые советы о том, как это сделать. Я хотел бы загрузить плагин и затем создать конечную точку WCF, которая зарегистрирована в IIS 7 для доступа к этому плагину.

Заранее спасибо:)

1 Ответ

11 голосов
/ 28 апреля 2011

Используя производную Работа службы WCF в Dynamic IIS от Darko , вы можете достичь того, чего хотите. Давайте начнем с примера сервиса, который мы могли бы разместить, мы назовем его IMessageBroker, контракт прост:

[ServiceContract]
public interface IMessageBroker
{
  [OperationContract]
  string Send(string message);
}

Мы используем этот контракт как для Сервиса, так и для MEF Экспорт / Импорт. Мы также определим некоторые дополнительные метаданные, которые будут сопровождаться этим:

public interface IMessageBrokerMetadata
{
  public string Name { get; }
  public string Channel { get; }
}

Поскольку это простой проект, я обманываю и использую простой статический класс для управления MEF CompositionContainer, используемым для составления деталей:

public static class MEF
{
    private static CompositionContainer container;
    private static bool initialised;

    public static void Initialise()
    {
        var catalog = new DirectoryCatalog("bin");
        container = new CompositionContainer(catalog);
        initialised = true;
    }

    public static CompositionContainer Container
    {
        get
        {
            if (!initialised) Initialise();
            return container;
        }
    }
}

Чтобы иметь возможность динамически генерировать службы WCF, нам нужно создать ServiceHostFactory, который может обращаться к нашему контейнеру композиции для доступа к нашим типам, поэтому вы можете сделать:

public class MEFServiceHostFactory : ServiceHostFactory
{
    public override ServiceHostBase CreateServiceHost(string constructorString, System.Uri[] baseAddresses)
    {
        var serviceType = MEF.Container
            .GetExports<IMessageBroker, IMessageBrokerMetadata>()
            .Where(l => l.Metadata.Name == constructorString)
            .Select(l => l.Value.GetType())
            .Single();

        var host = new ServiceHost(serviceType, baseAddresses);

        foreach (var contract in serviceType.GetInterfaces())
        {
            var attr = contract.GetCustomAttributes(typeof(ServiceContractAttribute), true).FirstOrDefault();
            if (attr != null)
                host.AddServiceEndpoint(contract, new BasicHttpBinding(), "");
        }

        var metadata = host.Description.Behaviors
            .OfType<ServiceMetadataBehavior>()
            .FirstOrDefault();

        if (metadata == null)
        {
            metadata = new ServiceMetadataBehavior();
            metadata.HttpGetEnabled = true;
            host.Description.Behaviors.Add(metadata);
        }
        else
        {
            metadata.HttpGetEnabled = true;
        }

        return host;
    }
}

По сути, аргумент constructorString используется для передачи имени метаданных, которое мы хотим для конкретной службы. Далее нам нужно разобраться с поиском этих сервисов. Теперь нам нужен VirtualPathProvider, который мы можем использовать для динамического создания экземпляра, через VirtualFile. Поставщик будет выглядеть так:

public class ServiceVirtualPathProvider : VirtualPathProvider
{
    private bool IsServiceCall(string virtualPath)
    {
        virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return (virtualPath.ToLower().StartsWith("~/services/"));
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        return IsServiceCall(virtualPath)
                   ? new ServiceFile(virtualPath)
                   : Previous.GetFile(virtualPath);
    }

    public override bool FileExists(string virtualPath)
    {
        if (IsServiceCall(virtualPath))
            return true;

        return Previous.FileExists(virtualPath);
    }

    public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        return IsServiceCall(virtualPath)
                   ? null
                   : Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }
}

То, что мы делаем, сопоставляет любые вызовы /Services/ с нашими конечными точками, полученными из MEF. Службе нужен виртуальный файл, и здесь мы связываем все это вместе:

public class ServiceFile : VirtualFile
{
    public ServiceFile(string virtualPath) : base(virtualPath)
    {

    }

    public string GetName(string virtualPath)
    {
        string filename = virtualPath.Substring(virtualPath.LastIndexOf("/") + 1);
        filename = filename.Substring(0, filename.LastIndexOf("."));

        return filename;
    }

    public override Stream Open()
    {
        var stream = new MemoryStream();
        var writer = new StreamWriter(stream);

        writer.Write("<%@ ServiceHost Language=\"C#\" Debug=\"true\" Service=\"" + GetName(VirtualPath) +
                     "\" Factory=\"Core.MEFServiceHostFactory, Core\" %>");
        writer.Flush();

        stream.Position = 0;
        return stream;
    }
}

Виртуальный файл выведет имя метаданных из виртуального пути, где /Services/SampleMessageBroker.svc -> SampleMessageBroker. Затем мы генерируем некоторую разметку, которая представляет разметку файла .svc с Service="SampleMessageBroker". Этот аргумент будет передан в MEFServiceHostFactory, где мы можем выбрать конечные точки. Итак, с учетом конечной точки образца:

[Export(typeof(IMessageBroker)),
 ExportMetadata("Name", "SampleMessageBroker"),
 ExportMetadata("Channel", "Greetings")]
public class SampleMessageBroker : IMessagerBroker
{
  public string Send(string message)
  {
    return "Hello! " + message;
  }
}

Теперь мы можем получить доступ к этому динамически в /Services/SampleMessageBroker.svc. Возможно, вы захотите предоставить статическую услугу, которая позволит вам определить, какие конечные точки доступны, и передать их своим потребляющим клиентам.

О, не забудьте подключить вашего провайдера виртуального пути:

HostingEnvironment.RegisterVirtualPathProvider(new ServiceVirtualPathProvider());
...