Используя производную Работа службы 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());