| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950 |
- //------------------------------------------------------------
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //------------------------------------------------------------
- namespace System.ServiceModel.Description
- {
- using System.Collections.Generic;
- using System.Globalization;
- using System.Net;
- using System.Reflection;
- using System.Runtime;
- using System.ServiceModel;
- using System.ServiceModel.Activation;
- using System.ServiceModel.Channels;
- using System.ServiceModel.Configuration;
- using System.ServiceModel.Dispatcher;
- using System.Threading;
- using System.Xml;
- using System.Xml.Schema;
- using WsdlNS = System.Web.Services.Description;
- // the description/metadata "mix-in"
- public class ServiceMetadataExtension : IExtension<ServiceHostBase>
- {
- const string BaseAddressPattern = "{%BaseAddress%}";
- static readonly Uri EmptyUri = new Uri(String.Empty, UriKind.Relative);
- static readonly Type[] httpGetSupportedChannels = new Type[] { typeof(IReplyChannel), };
- ServiceMetadataBehavior.MetadataExtensionInitializer initializer;
- MetadataSet metadata;
- WsdlNS.ServiceDescription singleWsdl;
- bool isInitialized = false;
- bool isSingleWsdlInitialized = false;
- Uri externalMetadataLocation;
- ServiceHostBase owner;
- object syncRoot = new object();
- object singleWsdlSyncRoot = new object();
- bool mexEnabled = false;
- bool httpGetEnabled = false;
- bool httpsGetEnabled = false;
- bool httpHelpPageEnabled = false;
- bool httpsHelpPageEnabled = false;
- Uri mexUrl;
- Uri httpGetUrl;
- Uri httpsGetUrl;
- Uri httpHelpPageUrl;
- Uri httpsHelpPageUrl;
- Binding httpHelpPageBinding;
- Binding httpsHelpPageBinding;
- Binding httpGetBinding;
- Binding httpsGetBinding;
- public ServiceMetadataExtension()
- : this(null)
- {
- }
- internal ServiceMetadataExtension(ServiceMetadataBehavior.MetadataExtensionInitializer initializer)
- {
- this.initializer = initializer;
- }
- internal ServiceMetadataBehavior.MetadataExtensionInitializer Initializer
- {
- get { return this.initializer; }
- set { this.initializer = value; }
- }
- public MetadataSet Metadata
- {
- get
- {
- EnsureInitialized();
- return this.metadata;
- }
- }
- public WsdlNS.ServiceDescription SingleWsdl
- {
- get
- {
- EnsureSingleWsdlInitialized();
- return this.singleWsdl;
- }
- }
- internal Uri ExternalMetadataLocation
- {
- get { return this.externalMetadataLocation; }
- set { this.externalMetadataLocation = value; }
- }
- internal bool MexEnabled
- {
- get { return this.mexEnabled; }
- set { this.mexEnabled = value; }
- }
- internal bool HttpGetEnabled
- {
- get { return this.httpGetEnabled; }
- set { this.httpGetEnabled = value; }
- }
- internal bool HttpsGetEnabled
- {
- get { return this.httpsGetEnabled; }
- set { this.httpsGetEnabled = value; }
- }
- internal bool HelpPageEnabled
- {
- get { return this.httpHelpPageEnabled || this.httpsHelpPageEnabled; }
- }
- internal bool MetadataEnabled
- {
- get { return this.mexEnabled || this.httpGetEnabled || this.httpsGetEnabled; }
- }
- internal bool HttpHelpPageEnabled
- {
- get { return this.httpHelpPageEnabled; }
- set { this.httpHelpPageEnabled = value; }
- }
- internal bool HttpsHelpPageEnabled
- {
- get { return this.httpsHelpPageEnabled; }
- set { this.httpsHelpPageEnabled = value; }
- }
- internal Uri MexUrl
- {
- get { return this.mexUrl; }
- set { this.mexUrl = value; }
- }
- internal Uri HttpGetUrl
- {
- get { return this.httpGetUrl; }
- set { this.httpGetUrl = value; }
- }
- internal Uri HttpsGetUrl
- {
- get { return this.httpsGetUrl; }
- set { this.httpsGetUrl = value; }
- }
- internal Uri HttpHelpPageUrl
- {
- get { return this.httpHelpPageUrl; }
- set { this.httpHelpPageUrl = value; }
- }
- internal Uri HttpsHelpPageUrl
- {
- get { return this.httpsHelpPageUrl; }
- set { this.httpsHelpPageUrl = value; }
- }
- internal Binding HttpHelpPageBinding
- {
- get { return this.httpHelpPageBinding; }
- set { this.httpHelpPageBinding = value; }
- }
- internal Binding HttpsHelpPageBinding
- {
- get { return this.httpsHelpPageBinding; }
- set { this.httpsHelpPageBinding = value; }
- }
- internal Binding HttpGetBinding
- {
- get { return this.httpGetBinding; }
- set { this.httpGetBinding = value; }
- }
- internal Binding HttpsGetBinding
- {
- get { return this.httpsGetBinding; }
- set { this.httpsGetBinding = value; }
- }
- internal bool UpdateAddressDynamically { get; set; }
- // This dictionary should not be mutated after open
- internal IDictionary<string, int> UpdatePortsByScheme { get; set; }
- internal static bool TryGetHttpHostAndPort(Uri listenUri, Message request, out string host, out int port)
- {
- host = null;
- port = 0;
- // Get the host hedaer
- object property;
- if (!request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out property))
- {
- return false;
- }
- HttpRequestMessageProperty httpRequest = property as HttpRequestMessageProperty;
- if (httpRequest == null)
- {
- return false;
- }
- string hostHeader = httpRequest.Headers[HttpRequestHeader.Host];
- if (string.IsNullOrEmpty(hostHeader))
- {
- return false;
- }
- // Split and validate the host and port
- string hostUriString = string.Concat(listenUri.Scheme, "://", hostHeader);
- Uri hostUri;
- if (!Uri.TryCreate(hostUriString, UriKind.Absolute, out hostUri))
- {
- return false;
- }
- host = hostUri.Host;
- port = hostUri.Port;
- return true;
- }
- void EnsureInitialized()
- {
- if (!this.isInitialized)
- {
- lock (this.syncRoot)
- {
- if (!this.isInitialized)
- {
- if (this.initializer != null)
- {
- // the following call will initialize this
- // it will use the Metadata property to do the initialization
- // this will call back into this method, but exit because isInitialized is set.
- // if other threads try to call these methods, they will block on the lock
- this.metadata = this.initializer.GenerateMetadata();
- }
- if (this.metadata == null)
- {
- this.metadata = new MetadataSet();
- }
- Thread.MemoryBarrier();
- this.isInitialized = true;
- this.initializer = null;
- }
- }
- }
- }
- void EnsureSingleWsdlInitialized()
- {
- if (!this.isSingleWsdlInitialized)
- {
- lock (this.singleWsdlSyncRoot)
- {
- if (!this.isSingleWsdlInitialized)
- {
- // Could throw NotSupportedException if multiple contract namespaces. Let the exception propagate to the dispatcher and show up on the html error page
- this.singleWsdl = WsdlHelper.GetSingleWsdl(this.Metadata);
- this.isSingleWsdlInitialized = true;
- }
- }
- }
- }
- void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner)
- {
- if (owner == null)
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("owner"));
- if (this.owner != null)
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.TheServiceMetadataExtensionInstanceCouldNot2_0)));
- owner.ThrowIfClosedOrOpened();
- this.owner = owner;
- }
- void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner)
- {
- if (owner == null)
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("owner");
- if (this.owner == null)
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.TheServiceMetadataExtensionInstanceCouldNot3_0)));
- if (this.owner != owner)
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("owner", SR.GetString(SR.TheServiceMetadataExtensionInstanceCouldNot4_0));
- this.owner.ThrowIfClosedOrOpened();
- this.owner = null;
- }
- static internal ServiceMetadataExtension EnsureServiceMetadataExtension(ServiceDescription description, ServiceHostBase host)
- {
- ServiceMetadataExtension mex = host.Extensions.Find<ServiceMetadataExtension>();
- if (mex == null)
- {
- mex = new ServiceMetadataExtension();
- host.Extensions.Add(mex);
- }
- return mex;
- }
- internal ChannelDispatcher EnsureGetDispatcher(Uri listenUri)
- {
- ChannelDispatcher channelDispatcher = FindGetDispatcher(listenUri);
- if (channelDispatcher == null)
- {
- channelDispatcher = CreateGetDispatcher(listenUri);
- owner.ChannelDispatchers.Add(channelDispatcher);
- }
- return channelDispatcher;
- }
- internal ChannelDispatcher EnsureGetDispatcher(Uri listenUri, bool isServiceDebugBehavior)
- {
- ChannelDispatcher channelDispatcher = FindGetDispatcher(listenUri);
- Binding binding;
- if (channelDispatcher == null)
- {
- if (listenUri.Scheme == Uri.UriSchemeHttp)
- {
- if (isServiceDebugBehavior)
- {
- binding = this.httpHelpPageBinding ?? MetadataExchangeBindings.HttpGet;
- }
- else
- {
- binding = this.httpGetBinding ?? MetadataExchangeBindings.HttpGet;
- }
- }
- else if (listenUri.Scheme == Uri.UriSchemeHttps)
- {
- if (isServiceDebugBehavior)
- {
- binding = this.httpsHelpPageBinding ?? MetadataExchangeBindings.HttpsGet;
- }
- else
- {
- binding = this.httpsGetBinding ?? MetadataExchangeBindings.HttpsGet;
- }
- }
- else
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SFxGetChannelDispatcherDoesNotSupportScheme, typeof(ChannelDispatcher).Name, Uri.UriSchemeHttp, Uri.UriSchemeHttps)));
- }
- channelDispatcher = CreateGetDispatcher(listenUri, binding);
- owner.ChannelDispatchers.Add(channelDispatcher);
- }
- return channelDispatcher;
- }
- ChannelDispatcher FindGetDispatcher(Uri listenUri)
- {
- foreach (ChannelDispatcherBase channelDispatcherBase in owner.ChannelDispatchers)
- {
- ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
- if (channelDispatcher != null && channelDispatcher.Listener.Uri == listenUri)
- {
- if (channelDispatcher.Endpoints.Count == 1 &&
- channelDispatcher.Endpoints[0].DispatchRuntime.SingletonInstanceContext != null &&
- channelDispatcher.Endpoints[0].DispatchRuntime.SingletonInstanceContext.UserObject is HttpGetImpl)
- {
- return channelDispatcher;
- }
- }
- }
- return null;
- }
- ChannelDispatcher CreateGetDispatcher(Uri listenUri)
- {
- if (listenUri.Scheme == Uri.UriSchemeHttp)
- {
- return CreateGetDispatcher(listenUri, MetadataExchangeBindings.HttpGet);
- }
- else if (listenUri.Scheme == Uri.UriSchemeHttps)
- {
- return CreateGetDispatcher(listenUri, MetadataExchangeBindings.HttpsGet);
- }
- else
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SFxGetChannelDispatcherDoesNotSupportScheme, typeof(ChannelDispatcher).Name, Uri.UriSchemeHttp, Uri.UriSchemeHttps)));
- }
- }
- ChannelDispatcher CreateGetDispatcher(Uri listenUri, Binding binding)
- {
- EndpointAddress address = new EndpointAddress(listenUri);
- Uri listenUriBaseAddress = listenUri;
- string listenUriRelativeAddress = string.Empty;
- //Set up binding parameter collection
- BindingParameterCollection parameters = owner.GetBindingParameters();
- AspNetEnvironment.Current.AddMetadataBindingParameters(listenUriBaseAddress, owner.Description.Behaviors, parameters);
- // find listener for HTTP GET
- IChannelListener listener = null;
- if (binding.CanBuildChannelListener<IReplyChannel>(parameters))
- {
- listener = binding.BuildChannelListener<IReplyChannel>(listenUriBaseAddress, listenUriRelativeAddress, parameters);
- }
- else
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SFxBindingNotSupportedForMetadataHttpGet)));
- }
- //create dispatchers
- ChannelDispatcher channelDispatcher = new ChannelDispatcher(listener, HttpGetImpl.MetadataHttpGetBinding, binding);
- channelDispatcher.MessageVersion = binding.MessageVersion;
- EndpointDispatcher dispatcher = new EndpointDispatcher(address, HttpGetImpl.ContractName, HttpGetImpl.ContractNamespace, true);
- //Add operation
- DispatchOperation operationDispatcher = new DispatchOperation(dispatcher.DispatchRuntime, HttpGetImpl.GetMethodName, HttpGetImpl.RequestAction, HttpGetImpl.ReplyAction);
- operationDispatcher.Formatter = MessageOperationFormatter.Instance;
- MethodInfo methodInfo = typeof(IHttpGetMetadata).GetMethod(HttpGetImpl.GetMethodName);
- operationDispatcher.Invoker = new SyncMethodInvoker(methodInfo);
- dispatcher.DispatchRuntime.Operations.Add(operationDispatcher);
- //wire up dispatchers
- HttpGetImpl impl = new HttpGetImpl(this, listener.Uri);
- dispatcher.DispatchRuntime.SingletonInstanceContext = new InstanceContext(owner, impl, false);
- dispatcher.DispatchRuntime.MessageInspectors.Add(impl);
- channelDispatcher.Endpoints.Add(dispatcher);
- dispatcher.ContractFilter = new MatchAllMessageFilter();
- dispatcher.FilterPriority = 0;
- dispatcher.DispatchRuntime.InstanceContextProvider = InstanceContextProviderBase.GetProviderForMode(InstanceContextMode.Single, dispatcher.DispatchRuntime);
- channelDispatcher.ServiceThrottle = owner.ServiceThrottle;
- ServiceDebugBehavior sdb = owner.Description.Behaviors.Find<ServiceDebugBehavior>();
- if (sdb != null)
- channelDispatcher.IncludeExceptionDetailInFaults |= sdb.IncludeExceptionDetailInFaults;
- ServiceBehaviorAttribute sba = owner.Description.Behaviors.Find<ServiceBehaviorAttribute>();
- if (sba != null)
- channelDispatcher.IncludeExceptionDetailInFaults |= sba.IncludeExceptionDetailInFaults;
- return channelDispatcher;
- }
- WriteFilter GetWriteFilter(Message request, Uri listenUri, bool removeBaseAddress)
- {
- WriteFilter result = null;
- if (this.UpdateAddressDynamically)
- {
- // Update address dynamically based on the request URI
- result = GetDynamicAddressWriter(request, listenUri, removeBaseAddress);
- }
- if (result == null)
- {
- // Just use the statically known listen URI
- if (removeBaseAddress)
- {
- result = new LocationUpdatingWriter(BaseAddressPattern, null);
- }
- else
- {
- result = new LocationUpdatingWriter(BaseAddressPattern, listenUri.ToString());
- }
- }
- return result;
- }
- DynamicAddressUpdateWriter GetDynamicAddressWriter(Message request, Uri listenUri, bool removeBaseAddress)
- {
- string requestHost;
- int requestPort;
- if (!TryGetHttpHostAndPort(listenUri, request, out requestHost, out requestPort))
- {
- if (request.Headers.To == null)
- {
- return null;
- }
- requestHost = request.Headers.To.Host;
- requestPort = request.Headers.To.Port;
- }
- // Perf optimization: don't do dynamic update if it would be a no-op.
- // Ordinal string comparison is okay; it just means we don't get the perf optimization
- // if the listen host and request host are case-insensitively equal.
- if (requestHost == listenUri.Host &&
- requestPort == listenUri.Port &&
- (UpdatePortsByScheme == null || UpdatePortsByScheme.Count == 0))
- {
- return null;
- }
- return new DynamicAddressUpdateWriter(
- listenUri, requestHost, requestPort, this.UpdatePortsByScheme, removeBaseAddress);
- }
- internal class MetadataBindingParameter { }
- internal class WSMexImpl : IMetadataExchange
- {
- internal const string MetadataMexBinding = "ServiceMetadataBehaviorMexBinding";
- internal const string ContractName = MetadataStrings.WSTransfer.Name;
- internal const string ContractNamespace = MetadataStrings.WSTransfer.Namespace;
- internal const string GetMethodName = "Get";
- internal const string RequestAction = MetadataStrings.WSTransfer.GetAction;
- internal const string ReplyAction = MetadataStrings.WSTransfer.GetResponseAction;
- ServiceMetadataExtension parent;
- MetadataSet metadataLocationSet;
- TypedMessageConverter converter;
- Uri listenUri;
- bool isListeningOnHttps;
- internal WSMexImpl(ServiceMetadataExtension parent, bool isListeningOnHttps, Uri listenUri)
- {
- this.parent = parent;
- this.isListeningOnHttps = isListeningOnHttps;
- this.listenUri = listenUri;
- if (this.parent.ExternalMetadataLocation != null && this.parent.ExternalMetadataLocation != EmptyUri)
- {
- this.metadataLocationSet = new MetadataSet();
- string location = this.GetLocationToReturn();
- MetadataSection metadataLocationSection = new MetadataSection(MetadataSection.ServiceDescriptionDialect, null, new MetadataLocation(location));
- this.metadataLocationSet.MetadataSections.Add(metadataLocationSection);
- }
- }
- internal bool IsListeningOnHttps
- {
- get { return this.isListeningOnHttps; }
- set { this.isListeningOnHttps = value; }
- }
- string GetLocationToReturn()
- {
- Fx.Assert(this.parent.ExternalMetadataLocation != null, "");
- Uri location = this.parent.ExternalMetadataLocation;
- if (!location.IsAbsoluteUri)
- {
- Uri httpAddr = parent.owner.GetVia(Uri.UriSchemeHttp, location);
- Uri httpsAddr = parent.owner.GetVia(Uri.UriSchemeHttps, location);
- if (this.IsListeningOnHttps && httpsAddr != null)
- {
- location = httpsAddr;
- }
- else if (httpAddr != null)
- {
- location = httpAddr;
- }
- else
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("ExternalMetadataLocation", SR.GetString(SR.SFxBadMetadataLocationNoAppropriateBaseAddress, this.parent.ExternalMetadataLocation.OriginalString));
- }
- }
- return location.ToString();
- }
- MetadataSet GatherMetadata(string dialect, string identifier)
- {
- if (metadataLocationSet != null)
- {
- return metadataLocationSet;
- }
- else
- {
- MetadataSet metadataSet = new MetadataSet();
- foreach (MetadataSection document in parent.Metadata.MetadataSections)
- {
- if ((dialect == null || dialect == document.Dialect) &&
- (identifier == null || identifier == document.Identifier))
- metadataSet.MetadataSections.Add(document);
- }
-
- return metadataSet;
- }
- }
- public Message Get(Message request)
- {
- GetResponse response = new GetResponse();
- response.Metadata = GatherMetadata(null, null);
- response.Metadata.WriteFilter = parent.GetWriteFilter(request, this.listenUri, true);
- if (converter == null)
- converter = TypedMessageConverter.Create(typeof(GetResponse), ReplyAction);
- return converter.ToMessage(response, request.Version);
- }
- public IAsyncResult BeginGet(Message request, AsyncCallback callback, object state)
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException());
- }
- public Message EndGet(IAsyncResult result)
- {
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException());
- }
- }
- //If this contract is changed, you may need to change ServiceMetadataExtension.CreateHttpGetDispatcher()
- [ServiceContract]
- internal interface IHttpGetMetadata
- {
- [OperationContract(Action = MessageHeaders.WildcardAction, ReplyAction = MessageHeaders.WildcardAction)]
- Message Get(Message msg);
- }
- internal class HttpGetImpl : IHttpGetMetadata, IDispatchMessageInspector
- {
- const string DiscoToken = "disco token";
- const string DiscoQueryString = "disco";
- const string WsdlQueryString = "wsdl";
- const string XsdQueryString = "xsd";
- const string SingleWsdlQueryString = "singleWsdl";
- const string HtmlContentType = "text/html; charset=UTF-8";
- const string XmlContentType = "text/xml; charset=UTF-8";
- const int closeTimeoutInSeconds = 90;
- const int maxQueryStringChars = 2048;
- internal const string MetadataHttpGetBinding = "ServiceMetadataBehaviorHttpGetBinding";
- internal const string ContractName = "IHttpGetHelpPageAndMetadataContract";
- internal const string ContractNamespace = "http://schemas.microsoft.com/2006/04/http/metadata";
- internal const string GetMethodName = "Get";
- internal const string RequestAction = MessageHeaders.WildcardAction;
- internal const string ReplyAction = MessageHeaders.WildcardAction;
- internal const string HtmlBreak = "<BR/>";
- static string[] NoQueries = new string[0];
- ServiceMetadataExtension parent;
- object sync = new object();
- InitializationData initData;
- Uri listenUri;
- bool helpPageEnabled = false;
- bool getWsdlEnabled = false;
- internal HttpGetImpl(ServiceMetadataExtension parent, Uri listenUri)
- {
- this.parent = parent;
- this.listenUri = listenUri;
- }
- public bool HelpPageEnabled
- {
- get { return this.helpPageEnabled; }
- set { this.helpPageEnabled = value; }
- }
- public bool GetWsdlEnabled
- {
- get { return this.getWsdlEnabled; }
- set { this.getWsdlEnabled = value; }
- }
- InitializationData GetInitData()
- {
- if (this.initData == null)
- {
- lock (this.sync)
- {
- if (this.initData == null)
- {
- this.initData = InitializationData.InitializeFrom(parent);
- }
- }
- }
- return this.initData;
- }
- string FindWsdlReference(DynamicAddressUpdateWriter addressUpdater)
- {
- if (this.parent.ExternalMetadataLocation == null || this.parent.ExternalMetadataLocation == EmptyUri)
- {
- return null;
- }
- else
- {
- Uri location = this.parent.ExternalMetadataLocation;
- Uri result = ServiceHost.GetUri(this.listenUri, location);
- if (addressUpdater != null)
- {
- addressUpdater.UpdateUri(ref result);
- }
- return result.ToString();
- }
- }
- bool TryHandleDocumentationRequest(Message httpGetRequest, string[] queries, out Message replyMessage)
- {
- replyMessage = null;
- if (!this.HelpPageEnabled)
- return false;
- if (parent.MetadataEnabled)
- {
- string discoUrl = null;
- string wsdlUrl = null;
- string httpGetUrl = null;
- string singleWsdlUrl = null;
- bool linkMetadata = true;
- DynamicAddressUpdateWriter addressUpdater = null;
- if (parent.UpdateAddressDynamically)
- {
- addressUpdater = parent.GetDynamicAddressWriter(httpGetRequest, this.listenUri, false);
- }
- wsdlUrl = FindWsdlReference(addressUpdater);
- httpGetUrl = GetHttpGetUrl(addressUpdater);
- if (wsdlUrl == null && httpGetUrl != null)
- {
- wsdlUrl = httpGetUrl + "?" + WsdlQueryString;
- singleWsdlUrl = httpGetUrl + "?" + SingleWsdlQueryString;
- }
- if (httpGetUrl != null)
- discoUrl = httpGetUrl + "?" + DiscoQueryString;
- if (wsdlUrl == null)
- {
- wsdlUrl = GetMexUrl(addressUpdater);
- linkMetadata = false;
- }
- replyMessage = new MetadataOnHelpPageMessage(discoUrl, wsdlUrl, singleWsdlUrl, GetInitData().ServiceName, GetInitData().ClientName, linkMetadata);
- }
- else
- {
- replyMessage = new MetadataOffHelpPageMessage(GetInitData().ServiceName);
- }
- AddHttpProperty(replyMessage, HttpStatusCode.OK, HtmlContentType);
- return true;
- }
- string GetHttpGetUrl(DynamicAddressUpdateWriter addressUpdater)
- {
- Uri result = null;
- if (this.listenUri.Scheme == Uri.UriSchemeHttp)
- {
- if (parent.HttpGetEnabled)
- result = parent.HttpGetUrl;
- else if (parent.HttpsGetEnabled)
- result = parent.HttpsGetUrl;
- }
- else
- {
- if (parent.HttpsGetEnabled)
- result = parent.HttpsGetUrl;
- else if (parent.HttpGetEnabled)
- result = parent.HttpGetUrl;
- }
- if (result != null)
- {
- if (addressUpdater != null)
- {
- addressUpdater.UpdateUri(ref result, this.listenUri.Scheme != result.Scheme /*updateBaseAddressOnly*/);
- }
- return result.ToString();
- }
- return null;
- }
- string GetMexUrl(DynamicAddressUpdateWriter addressUpdater)
- {
- if (parent.MexEnabled)
- {
- Uri result = parent.MexUrl;
- if (addressUpdater != null)
- {
- addressUpdater.UpdateUri(ref result);
- }
- return result.ToString();
- }
- return null;
- }
- bool TryHandleMetadataRequest(Message httpGetRequest, string[] queries, out Message replyMessage)
- {
- replyMessage = null;
- if (!this.GetWsdlEnabled)
- return false;
- WriteFilter writeFilter = parent.GetWriteFilter(httpGetRequest, this.listenUri, false);
- string query = FindQuery(queries);
- if (String.IsNullOrEmpty(query))
- {
- //if the documentation page is not available return the default wsdl if it exists
- if (!this.helpPageEnabled && GetInitData().DefaultWsdl != null)
- {
- // use the default WSDL
- using (httpGetRequest)
- {
- replyMessage = new ServiceDescriptionMessage(
- GetInitData().DefaultWsdl, writeFilter);
- AddHttpProperty(replyMessage, HttpStatusCode.OK, XmlContentType);
- GetInitData().FixImportAddresses();
- return true;
- }
- }
- return false;
- }
- // try to look the document up in the query table
- object doc;
- if (GetInitData().TryQueryLookup(query, out doc))
- {
- using (httpGetRequest)
- {
- if (doc is WsdlNS.ServiceDescription)
- {
- replyMessage = new ServiceDescriptionMessage(
- (WsdlNS.ServiceDescription)doc, writeFilter);
- }
- else if (doc is XmlSchema)
- {
- replyMessage = new XmlSchemaMessage(
- ((XmlSchema)doc), writeFilter);
- }
- else if (doc is string)
- {
- if (((string)doc) == DiscoToken)
- {
- replyMessage = CreateDiscoMessage(writeFilter as DynamicAddressUpdateWriter);
- }
- else
- {
- Fx.Assert("Bad object in HttpGetImpl docFromQuery table");
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Bad object in HttpGetImpl docFromQuery table")));
- }
- }
- else
- {
- Fx.Assert("Bad object in HttpGetImpl docFromQuery table");
- throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Bad object in HttpGetImpl docFromQuery table")));
- }
- AddHttpProperty(replyMessage, HttpStatusCode.OK, XmlContentType);
- GetInitData().FixImportAddresses();
- return true;
- }
- }
- // otherwise see if they just wanted ?WSDL
- if (String.Compare(query, WsdlQueryString, StringComparison.OrdinalIgnoreCase) == 0)
- {
- if (GetInitData().DefaultWsdl != null)
- {
- // use the default WSDL
- using (httpGetRequest)
- {
- replyMessage = new ServiceDescriptionMessage(
- GetInitData().DefaultWsdl, writeFilter);
- AddHttpProperty(replyMessage, HttpStatusCode.OK, XmlContentType);
- GetInitData().FixImportAddresses();
- return true;
- }
- }
- // or redirect to an external WSDL
- string wsdlReference = FindWsdlReference(writeFilter as DynamicAddressUpdateWriter);
- if (wsdlReference != null)
- {
- replyMessage = CreateRedirectMessage(wsdlReference);
- return true;
- }
- }
- // ?singleWSDL
- if (String.Compare(query, SingleWsdlQueryString, StringComparison.OrdinalIgnoreCase) == 0)
- {
- WsdlNS.ServiceDescription singleWSDL = parent.SingleWsdl;
- if (singleWSDL != null)
- {
- using (httpGetRequest)
- {
- replyMessage = new ServiceDescriptionMessage(
- singleWSDL, writeFilter);
- AddHttpProperty(replyMessage, HttpStatusCode.OK, XmlContentType);
- return true;
- }
- }
- }
- // we weren't able to handle the request -- return the documentation page if available
- return false;
- }
- Message CreateDiscoMessage(DynamicAddressUpdateWriter addressUpdater)
- {
- Uri wsdlUrlBase = this.listenUri;
- if (addressUpdater != null)
- {
- addressUpdater.UpdateUri(ref wsdlUrlBase);
- }
- string wsdlUrl = wsdlUrlBase.ToString() + "?" + WsdlQueryString;
- Uri docUrl = null;
- if (this.listenUri.Scheme == Uri.UriSchemeHttp)
- {
- if (parent.HttpHelpPageEnabled)
- docUrl = parent.HttpHelpPageUrl;
- else if (parent.HttpsHelpPageEnabled)
- docUrl = parent.HttpsGetUrl;
- }
- else
- {
- if (parent.HttpsHelpPageEnabled)
- docUrl = parent.HttpsHelpPageUrl;
- else if (parent.HttpHelpPageEnabled)
- docUrl = parent.HttpGetUrl;
- }
- if (addressUpdater != null)
- {
- addressUpdater.UpdateUri(ref docUrl);
- }
- return new DiscoMessage(wsdlUrl, docUrl.ToString());
- }
- string FindQuery(string[] queries)
- {
- string query = null;
- foreach (string q in queries)
- {
- int start = (q.Length > 0 && q[0] == '?') ? 1 : 0;
- if (String.Compare(q, start, WsdlQueryString, 0, WsdlQueryString.Length, StringComparison.OrdinalIgnoreCase) == 0)
- query = q;
- else if (String.Compare(q, start, XsdQueryString, 0, XsdQueryString.Length, StringComparison.OrdinalIgnoreCase) == 0)
- query = q;
- else if (String.Compare(q, start, SingleWsdlQueryString, 0, SingleWsdlQueryString.Length, StringComparison.OrdinalIgnoreCase) == 0)
- query = q;
- else if (parent.HelpPageEnabled && (String.Compare(q, start, DiscoQueryString, 0, DiscoQueryString.Length, StringComparison.OrdinalIgnoreCase) == 0))
- query = q;
- }
- return query;
- }
- Message ProcessHttpRequest(Message httpGetRequest)
- {
- string queryString = httpGetRequest.Properties.Via.Query;
- if (queryString.Length > maxQueryStringChars)
- return CreateHttpResponseMessage(HttpStatusCode.RequestUriTooLong);
- if (queryString.StartsWith("?", StringComparison.OrdinalIgnoreCase))
- queryString = queryString.Substring(1);
- string[] queries = queryString.Length > 0 ? queryString.Split('&') : NoQueries;
- Message replyMessage = null;
- if (TryHandleMetadataRequest(httpGetRequest, queries, out replyMessage))
- return replyMessage;
- if (TryHandleDocumentationRequest(httpGetRequest, queries, out replyMessage))
- return replyMessage;
- return CreateHttpResponseMessage(HttpStatusCode.MethodNotAllowed);
- }
- public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
- {
- return request.Version;
- }
- public void BeforeSendReply(ref Message reply, object correlationState)
- {
- if ((reply != null) && reply.IsFault)
- {
- string error = SR.GetString(SR.SFxInternalServerError);
- ExceptionDetail exceptionDetail = null;
- MessageFault fault = MessageFault.CreateFault(reply, /* maxBufferSize */ 64 * 1024);
- if (fault.HasDetail)
- {
- exceptionDetail = fault.GetDetail<ExceptionDetail>();
- if (exceptionDetail != null)
- {
- error = SR.GetString(SR.SFxDocExt_Error);
- }
- }
- reply = new MetadataOnHelpPageMessage(error, exceptionDetail);
- AddHttpProperty(reply, HttpStatusCode.InternalServerError, HtmlContentType);
- }
- }
- public Message Get(Message message)
- {
- return ProcessHttpRequest(message);
- }
- class InitializationData
- {
- readonly Dictionary<string, object> docFromQuery;
- readonly Dictionary<object, string> queryFromDoc;
- WsdlNS.ServiceDescriptionCollection wsdls;
- XmlSchemaSet xsds;
- public string ServiceName;
- public string ClientName;
- public WsdlNS.ServiceDescription DefaultWsdl;
- InitializationData(
- Dictionary<string, object> docFromQuery,
- Dictionary<object, string> queryFromDoc,
- WsdlNS.ServiceDescriptionCollection wsdls,
- XmlSchemaSet xsds)
- {
- this.docFromQuery = docFromQuery;
- this.queryFromDoc = queryFromDoc;
- this.wsdls = wsdls;
- this.xsds = xsds;
- }
- public bool TryQueryLookup(string query, out object doc)
- {
- return docFromQuery.TryGetValue(query, out doc);
- }
- public static InitializationData InitializeFrom(ServiceMetadataExtension extension)
- {
- Dictionary<string, object> docFromQueryInit = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
- Dictionary<object, string> queryFromDocInit = new Dictionary<object, string>();
- // this collection type provides useful lookup features
- WsdlNS.ServiceDescriptionCollection wsdls = CollectWsdls(extension.Metadata);
- XmlSchemaSet xsds = CollectXsds(extension.Metadata);
- WsdlNS.ServiceDescription defaultWsdl = null;
- WsdlNS.Service someService = GetAnyService(wsdls);
- if (someService != null)
- defaultWsdl = someService.ServiceDescription;
- // WSDLs
- {
- int i = 0;
- foreach (WsdlNS.ServiceDescription wsdlDoc in wsdls)
- {
- string query = WsdlQueryString;
- if (wsdlDoc != defaultWsdl) // don't count the WSDL at ?WSDL
- query += "=wsdl" + (i++).ToString(System.Globalization.CultureInfo.InvariantCulture);
- docFromQueryInit.Add(query, wsdlDoc);
- queryFromDocInit.Add(wsdlDoc, query);
- }
- }
- // XSDs
- {
- int i = 0;
- foreach (XmlSchema xsdDoc in xsds.Schemas())
- {
- string query = XsdQueryString + "=xsd" + (i++).ToString(System.Globalization.CultureInfo.InvariantCulture);
- docFromQueryInit.Add(query, xsdDoc);
- queryFromDocInit.Add(xsdDoc, query);
- }
- }
- // Disco
- if (extension.HelpPageEnabled)
- {
- string query = DiscoQueryString;
- docFromQueryInit.Add(query, DiscoToken);
- queryFromDocInit.Add(DiscoToken, query);
- }
- InitializationData data = new InitializationData(docFromQueryInit, queryFromDocInit, wsdls, xsds);
- data.DefaultWsdl = defaultWsdl;
- data.ServiceName = GetAnyWsdlName(wsdls);
- data.ClientName = ClientClassGenerator.GetClientClassName(GetAnyContractName(wsdls) ?? "IHello");
- return data;
- }
- static WsdlNS.ServiceDescriptionCollection CollectWsdls(MetadataSet metadata)
- {
- WsdlNS.ServiceDescriptionCollection wsdls = new WsdlNS.ServiceDescriptionCollection();
- foreach (MetadataSection section in metadata.MetadataSections)
- {
- if (section.Metadata is WsdlNS.ServiceDescription)
- {
- wsdls.Add((WsdlNS.ServiceDescription)section.Metadata);
- }
- }
- return wsdls;
- }
- static XmlSchemaSet CollectXsds(MetadataSet metadata)
- {
- XmlSchemaSet xsds = new XmlSchemaSet();
- xsds.XmlResolver = null;
- foreach (MetadataSection section in metadata.MetadataSections)
- {
- if (section.Metadata is XmlSchema)
- {
- xsds.Add((XmlSchema)section.Metadata);
- }
- }
- return xsds;
- }
- internal void FixImportAddresses()
- {
- // fixup imports and includes with addresses
- // WSDLs
- foreach (WsdlNS.ServiceDescription wsdlDoc in this.wsdls)
- {
- FixImportAddresses(wsdlDoc);
- }
- // XSDs
- foreach (XmlSchema xsdDoc in this.xsds.Schemas())
- {
- FixImportAddresses(xsdDoc);
- }
- }
- void FixImportAddresses(WsdlNS.ServiceDescription wsdlDoc)
- {
- foreach (WsdlNS.Import import in wsdlDoc.Imports)
- {
- if (!String.IsNullOrEmpty(import.Location)) continue;
- WsdlNS.ServiceDescription targetDoc = this.wsdls[import.Namespace ?? String.Empty];
- if (targetDoc != null)
- {
- string query = queryFromDoc[targetDoc];
- import.Location = BaseAddressPattern + "?" + query;
- }
- }
- if (wsdlDoc.Types != null)
- {
- foreach (XmlSchema xsdDoc in wsdlDoc.Types.Schemas)
- {
- FixImportAddresses(xsdDoc);
- }
- }
- }
- void FixImportAddresses(XmlSchema xsdDoc)
- {
- foreach (XmlSchemaObject o in xsdDoc.Includes)
- {
- XmlSchemaExternal external = o as XmlSchemaExternal;
- if (external == null || !String.IsNullOrEmpty(external.SchemaLocation)) continue;
- string targetNs = external is XmlSchemaImport ? ((XmlSchemaImport)external).Namespace : xsdDoc.TargetNamespace;
- foreach (XmlSchema targetXsd in this.xsds.Schemas(targetNs ?? String.Empty))
- {
- if (targetXsd != xsdDoc)
- {
- string query = this.queryFromDoc[targetXsd];
- external.SchemaLocation = BaseAddressPattern + "?" + query;
- break;
- }
- }
- }
- }
- static string GetAnyContractName(WsdlNS.ServiceDescriptionCollection wsdls)
- {
- // try to track down a WSDL portType name using a wsdl:service as a starting point
- foreach (WsdlNS.ServiceDescription wsdl in wsdls)
- {
- foreach (WsdlNS.Service service in wsdl.Services)
- {
- foreach (WsdlNS.Port port in service.Ports)
- {
- if (!port.Binding.IsEmpty)
- {
- WsdlNS.Binding binding = wsdls.GetBinding(port.Binding);
- if (!binding.Type.IsEmpty)
- {
- return binding.Type.Name;
- }
- }
- }
- }
- }
- return null;
- }
- static WsdlNS.Service GetAnyService(WsdlNS.ServiceDescriptionCollection wsdls)
- {
- // try to track down a WSDL service
- foreach (WsdlNS.ServiceDescription wsdl in wsdls)
- {
- if (wsdl.Services.Count > 0)
- {
- return wsdl.Services[0];
- }
- }
- return null;
- }
- static string GetAnyWsdlName(WsdlNS.ServiceDescriptionCollection wsdls)
- {
- // try to track down a WSDL name
- foreach (WsdlNS.ServiceDescription wsdl in wsdls)
- {
- if (!String.IsNullOrEmpty(wsdl.Name))
- {
- return wsdl.Name;
- }
- }
- return null;
- }
- }
- #region static helpers
- static void AddHttpProperty(Message message, HttpStatusCode status, string contentType)
- {
- HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
- responseProperty.StatusCode = status;
- responseProperty.Headers.Add(HttpResponseHeader.ContentType, contentType);
- message.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
- }
- static Message CreateRedirectMessage(string redirectedDestination)
- {
- Message redirectMessage = CreateHttpResponseMessage(HttpStatusCode.RedirectKeepVerb);
- HttpResponseMessageProperty httpResponseProperty = (HttpResponseMessageProperty)redirectMessage.Properties[HttpResponseMessageProperty.Name];
- httpResponseProperty.Headers["Location"] = redirectedDestination;
- return redirectMessage;
- }
- static Message CreateHttpResponseMessage(HttpStatusCode code)
- {
- Message message = new NullMessage();
- HttpResponseMessageProperty httpResponseProperty = new HttpResponseMessageProperty();
- httpResponseProperty.StatusCode = code;
- message.Properties.Add(HttpResponseMessageProperty.Name, httpResponseProperty);
- return message;
- }
- #endregion static helpers
- #region Helper Message implementations
- class DiscoMessage : ContentOnlyMessage
- {
- string wsdlAddress;
- string docAddress;
- public DiscoMessage(string wsdlAddress, string docAddress)
- : base()
- {
- this.wsdlAddress = wsdlAddress;
- this.docAddress = docAddress;
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- writer.WriteStartDocument();
- writer.WriteStartElement("discovery", "http://schemas.xmlsoap.org/disco/");
- writer.WriteStartElement("contractRef", "http://schemas.xmlsoap.org/disco/scl/");
- writer.WriteAttributeString("ref", wsdlAddress);
- writer.WriteAttributeString("docRef", docAddress);
- writer.WriteEndElement(); // </contractRef>
- writer.WriteEndElement(); // </discovery>
- writer.WriteEndDocument();
- }
- }
- class MetadataOnHelpPageMessage : ContentOnlyMessage
- {
- string discoUrl;
- string metadataUrl;
- string singleWsdlUrl;
- string serviceName;
- string clientName;
- bool linkMetadata;
- string errorMessage;
- ExceptionDetail exceptionDetail;
- public MetadataOnHelpPageMessage(string discoUrl, string metadataUrl, string singleWsdlUrl, string serviceName, string clientName, bool linkMetadata)
- : base()
- {
- this.discoUrl = discoUrl;
- this.metadataUrl = metadataUrl;
- this.singleWsdlUrl = singleWsdlUrl;
- this.serviceName = serviceName;
- this.clientName = clientName;
- this.linkMetadata = linkMetadata;
- }
- public MetadataOnHelpPageMessage(string errorMessage, ExceptionDetail exceptionDetail)
- : base()
- {
- this.errorMessage = errorMessage;
- this.exceptionDetail = exceptionDetail;
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- HelpPageWriter page = new HelpPageWriter(writer);
- writer.WriteStartElement("HTML");
- writer.WriteStartElement("HEAD");
- if (!String.IsNullOrEmpty(this.discoUrl))
- {
- page.WriteDiscoLink(this.discoUrl);
- }
- page.WriteStyleSheet();
- page.WriteTitle(!String.IsNullOrEmpty(this.serviceName) ? SR.GetString(SR.SFxDocExt_MainPageTitle, this.serviceName) : SR.GetString(SR.SFxDocExt_MainPageTitleNoServiceName));
- if (!String.IsNullOrEmpty(this.errorMessage))
- {
- page.WriteError(this.errorMessage);
- if (this.exceptionDetail != null)
- {
- page.WriteExceptionDetail(this.exceptionDetail);
- }
- }
- else
- {
- page.WriteToolUsage(this.metadataUrl, this.singleWsdlUrl, this.linkMetadata);
- page.WriteSampleCode(this.clientName);
- }
- writer.WriteEndElement(); // BODY
- writer.WriteEndElement(); // HTML
- }
- struct HelpPageWriter
- {
- XmlWriter writer;
- public HelpPageWriter(XmlWriter writer)
- {
- this.writer = writer;
- }
- internal void WriteClass(string className)
- {
- writer.WriteStartElement("font");
- writer.WriteAttributeString("color", "teal");
- writer.WriteString(className);
- writer.WriteEndElement(); // font
- }
- internal void WriteComment(string comment)
- {
- writer.WriteStartElement("font");
- writer.WriteAttributeString("color", "green");
- writer.WriteString(comment);
- writer.WriteEndElement(); // font
- }
- internal void WriteDiscoLink(string discoUrl)
- {
- writer.WriteStartElement("link");
- writer.WriteAttributeString("rel", "alternate");
- writer.WriteAttributeString("type", "text/xml");
- writer.WriteAttributeString("href", discoUrl);
- writer.WriteEndElement(); // link
- }
- internal void WriteError(string message)
- {
- writer.WriteStartElement("P");
- writer.WriteAttributeString("class", "intro");
- writer.WriteString(message);
- writer.WriteEndElement(); // P
- }
- internal void WriteKeyword(string keyword)
- {
- writer.WriteStartElement("font");
- writer.WriteAttributeString("color", "blue");
- writer.WriteString(keyword);
- writer.WriteEndElement(); // font
- }
- internal void WriteSampleCode(string clientName)
- {
- writer.WriteStartElement("P");
- writer.WriteAttributeString("class", "intro");
- writer.WriteEndElement(); // P
- writer.WriteRaw(SR.GetString(SR.SFxDocExt_MainPageIntro2));
- // C#
- writer.WriteRaw(SR.GetString(SR.SFxDocExt_CS));
- writer.WriteStartElement("PRE");
- WriteKeyword("class ");
- WriteClass("Test\n");
- writer.WriteString("{\n");
- WriteKeyword(" static void ");
- writer.WriteString("Main()\n");
- writer.WriteString(" {\n");
- writer.WriteString(" ");
- WriteClass(clientName);
- writer.WriteString(" client = ");
- WriteKeyword("new ");
- WriteClass(clientName);
- writer.WriteString("();\n\n");
- WriteComment(" // " + SR.GetString(SR.SFxDocExt_MainPageComment) + "\n\n");
- WriteComment(" // " + SR.GetString(SR.SFxDocExt_MainPageComment2) + "\n");
- writer.WriteString(" client.Close();\n");
- writer.WriteString(" }\n");
- writer.WriteString("}\n");
- writer.WriteEndElement(); // PRE
- writer.WriteRaw(HttpGetImpl.HtmlBreak);
- // VB
- writer.WriteRaw(SR.GetString(SR.SFxDocExt_VB));
- writer.WriteStartElement("PRE");
- WriteKeyword("Class ");
- WriteClass("Test\n");
- WriteKeyword(" Shared Sub ");
- writer.WriteString("Main()\n");
- WriteKeyword(" Dim ");
- writer.WriteString("client As ");
- WriteClass(clientName);
- writer.WriteString(" = ");
- WriteKeyword("New ");
- WriteClass(clientName);
- writer.WriteString("()\n");
- WriteComment(" ' " + SR.GetString(SR.SFxDocExt_MainPageComment) + "\n\n");
- WriteComment(" ' " + SR.GetString(SR.SFxDocExt_MainPageComment2) + "\n");
- writer.WriteString(" client.Close()\n");
- WriteKeyword(" End Sub\n");
- WriteKeyword("End Class");
- writer.WriteEndElement(); // PRE
- }
- internal void WriteExceptionDetail(ExceptionDetail exceptionDetail)
- {
- writer.WriteStartElement("PRE");
- writer.WriteString(exceptionDetail.ToString().Replace("\r", ""));
- writer.WriteEndElement(); // PRE
- }
- internal void WriteStyleSheet()
- {
- writer.WriteStartElement("STYLE");
- writer.WriteAttributeString("type", "text/css");
- writer.WriteString("#content{ FONT-SIZE: 0.7em; PADDING-BOTTOM: 2em; MARGIN-LEFT: 30px}");
- writer.WriteString("BODY{MARGIN-TOP: 0px; MARGIN-LEFT: 0px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: white}");
- writer.WriteString("P{MARGIN-TOP: 0px; MARGIN-BOTTOM: 12px; COLOR: #000000; FONT-FAMILY: Verdana}");
- writer.WriteString("PRE{BORDER-RIGHT: #f0f0e0 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #f0f0e0 1px solid; MARGIN-TOP: -5px; PADDING-LEFT: 5px; FONT-SIZE: 1.2em; PADDING-BOTTOM: 5px; BORDER-LEFT: #f0f0e0 1px solid; PADDING-TOP: 5px; BORDER-BOTTOM: #f0f0e0 1px solid; FONT-FAMILY: Courier New; BACKGROUND-COLOR: #e5e5cc}");
- writer.WriteString(".heading1{MARGIN-TOP: 0px; PADDING-LEFT: 15px; FONT-WEIGHT: normal; FONT-SIZE: 26px; MARGIN-BOTTOM: 0px; PADDING-BOTTOM: 3px; MARGIN-LEFT: -30px; WIDTH: 100%; COLOR: #ffffff; PADDING-TOP: 10px; FONT-FAMILY: Tahoma; BACKGROUND-COLOR: #003366}");
- writer.WriteString(".intro{MARGIN-LEFT: -15px}");
- writer.WriteEndElement(); // STYLE
- }
- internal void WriteTitle(string title)
- {
- writer.WriteElementString("TITLE", title);
- writer.WriteEndElement(); // HEAD
- writer.WriteStartElement("BODY");
- writer.WriteStartElement("DIV");
- writer.WriteAttributeString("id", "content");
- writer.WriteStartElement("P");
- writer.WriteAttributeString("class", "heading1");
- writer.WriteString(title);
- writer.WriteEndElement(); // P
- writer.WriteRaw(HttpGetImpl.HtmlBreak);
- }
- internal void WriteToolUsage(string wsdlUrl, string singleWsdlUrl, bool linkMetadata)
- {
- writer.WriteStartElement("P");
- writer.WriteAttributeString("class", "intro");
- if (wsdlUrl != null)
- {
- WriteMetadataAddress(SR.SFxDocExt_MainPageIntro1a, "svcutil.exe ", wsdlUrl, linkMetadata);
- if (singleWsdlUrl != null)
- {
- // ?singleWsdl message
- writer.WriteStartElement("P");
- WriteMetadataAddress(SR.SFxDocExt_MainPageIntroSingleWsdl, null, singleWsdlUrl, linkMetadata);
- writer.WriteEndElement();
- }
- }
- else
- {
- // no metadata message
- writer.WriteRaw(SR.GetString(SR.SFxDocExt_MainPageIntro1b));
- }
- writer.WriteEndElement(); // P
- }
- void WriteMetadataAddress(string introductionText, string clientToolName, string wsdlUrl, bool linkMetadata)
- {
- writer.WriteRaw(SR.GetString(introductionText));
- writer.WriteRaw(HttpGetImpl.HtmlBreak);
- writer.WriteStartElement("PRE");
- if (!string.IsNullOrEmpty(clientToolName))
- {
- writer.WriteString(clientToolName);
- }
- if (linkMetadata)
- {
- writer.WriteStartElement("A");
- writer.WriteAttributeString("HREF", wsdlUrl);
- }
- writer.WriteString(wsdlUrl);
- if (linkMetadata)
- {
- writer.WriteEndElement(); // A
- }
- writer.WriteEndElement(); // PRE
- }
- }
- }
- class MetadataOffHelpPageMessage : ContentOnlyMessage
- {
- public MetadataOffHelpPageMessage(string serviceName)
- {
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- writer.WriteStartElement("HTML");
- writer.WriteStartElement("HEAD");
- writer.WriteRaw(String.Format(CultureInfo.InvariantCulture,
- @"<STYLE type=""text/css"">#content{{ FONT-SIZE: 0.7em; PADDING-BOTTOM: 2em; MARGIN-LEFT: 30px}}BODY{{MARGIN-TOP: 0px; MARGIN-LEFT: 0px; COLOR: #000000; FONT-FAMILY: Verdana; BACKGROUND-COLOR: white}}P{{MARGIN-TOP: 0px; MARGIN-BOTTOM: 12px; COLOR: #000000; FONT-FAMILY: Verdana}}PRE{{BORDER-RIGHT: #f0f0e0 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #f0f0e0 1px solid; MARGIN-TOP: -5px; PADDING-LEFT: 5px; FONT-SIZE: 1.2em; PADDING-BOTTOM: 5px; BORDER-LEFT: #f0f0e0 1px solid; PADDING-TOP: 5px; BORDER-BOTTOM: #f0f0e0 1px solid; FONT-FAMILY: Courier New; BACKGROUND-COLOR: #e5e5cc}}.heading1{{MARGIN-TOP: 0px; PADDING-LEFT: 15px; FONT-WEIGHT: normal; FONT-SIZE: 26px; MARGIN-BOTTOM: 0px; PADDING-BOTTOM: 3px; MARGIN-LEFT: -30px; WIDTH: 100%; COLOR: #ffffff; PADDING-TOP: 10px; FONT-FAMILY: Tahoma; BACKGROUND-COLOR: #003366}}.intro{{MARGIN-LEFT: -15px}}</STYLE>
- <TITLE>Service</TITLE>"));
- writer.WriteEndElement(); //HEAD
- writer.WriteRaw(String.Format(CultureInfo.InvariantCulture,
- @"<BODY>
- <DIV id=""content"">
- <P class=""heading1"">Service</P>
- <BR/>
- <P class=""intro"">{0}</P>
- <PRE>
- <font color=""blue""><<font color=""darkred"">" + ConfigurationStrings.BehaviorsSectionName + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.ServiceBehaviors + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.Behavior + @" </font><font color=""red"">" + ConfigurationStrings.Name + @"</font>=<font color=""black"">""</font>MyServiceTypeBehaviors<font color=""black"">"" </font>></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.ServiceMetadataPublishingSectionName + @" </font><font color=""red"">" + ConfigurationStrings.HttpGetEnabled + @"</font>=<font color=""black"">""</font>true<font color=""black"">"" </font>/></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.Behavior + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.ServiceBehaviors + @"</font>></font>
- <font color=""blue""><<font color=""darkred"">/" + ConfigurationStrings.BehaviorsSectionName + @"</font>></font>
- </PRE>
- <P class=""intro"">{1}</P>
- <PRE>
- <font color=""blue""><<font color=""darkred"">" + ConfigurationStrings.Service + @" </font><font color=""red"">" + ConfigurationStrings.Name + @"</font>=<font color=""black"">""</font><i>MyNamespace.MyServiceType</i><font color=""black"">"" </font><font color=""red"">" + ConfigurationStrings.BehaviorConfiguration + @"</font>=<font color=""black"">""</font><i>MyServiceTypeBehaviors</i><font color=""black"">"" </font>></font>
- </PRE>
- <P class=""intro"">{2}</P>
- <PRE>
- <font color=""blue""><<font color=""darkred"">" + ConfigurationStrings.Endpoint + @" </font><font color=""red"">" + ConfigurationStrings.Contract + @"</font>=<font color=""black"">""</font>" + ServiceMetadataBehavior.MexContractName + @"<font color=""black"">"" </font><font color=""red"">" + ConfigurationStrings.Binding + @"</font>=<font color=""black"">""</font>mexHttpBinding<font color=""black"">"" </font><font color=""red"">" + ConfigurationStrings.Address + @"</font>=<font color=""black"">""</font>mex<font color=""black"">"" </font>/></font>
- </PRE>
- <P class=""intro"">{3}</P>
- <PRE>
- <font color=""blue""><<font color=""darkred"">configuration</font>></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.SectionGroupName + @"</font>></font>
-
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.ServicesSectionName + @"</font>></font>
- <font color=""blue""> <!-- <font color=""green"">{4}</font> --></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.Service + @" </font><font color=""red"">" + ConfigurationStrings.Name + @"</font>=<font color=""black"">""</font><i>MyNamespace.MyServiceType</i><font color=""black"">"" </font><font color=""red"">" + ConfigurationStrings.BehaviorConfiguration + @"</font>=<font color=""black"">""</font><i>MyServiceTypeBehaviors</i><font color=""black"">"" </font>></font>
- <font color=""blue""> <!-- <font color=""green"">{5}</font> --></font>
- <font color=""blue""> <!-- <font color=""green"">{6}</font> --></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.Endpoint + @" </font><font color=""red"">" + ConfigurationStrings.Contract + @"</font>=<font color=""black"">""</font>" + ServiceMetadataBehavior.MexContractName + @"<font color=""black"">"" </font><font color=""red"">" + ConfigurationStrings.Binding + @"</font>=<font color=""black"">""</font>mexHttpBinding<font color=""black"">"" </font><font color=""red"">" + ConfigurationStrings.Address + @"</font>=<font color=""black"">""</font>mex<font color=""black"">"" </font>/></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.Service + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.ServicesSectionName + @"</font>></font>
-
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.BehaviorsSectionName + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.ServiceBehaviors + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.Behavior + @" </font><font color=""red"">name</font>=<font color=""black"">""</font><i>MyServiceTypeBehaviors</i><font color=""black"">"" </font>></font>
- <font color=""blue""> <!-- <font color=""green"">{7}</font> --></font>
- <font color=""blue""> <<font color=""darkred"">" + ConfigurationStrings.ServiceMetadataPublishingSectionName + @" </font><font color=""red"">" + ConfigurationStrings.HttpGetEnabled + @"</font>=<font color=""black"">""</font>true<font color=""black"">"" </font>/></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.Behavior + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.ServiceBehaviors + @"</font>></font>
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.BehaviorsSectionName + @"</font>></font>
-
- <font color=""blue""> <<font color=""darkred"">/" + ConfigurationStrings.SectionGroupName + @"</font>></font>
- <font color=""blue""><<font color=""darkred"">/configuration</font>></font>
- </PRE>
- <P class=""intro"">{8}</P>
- </DIV>
- </BODY>",
- SR.GetString(SR.SFxDocExt_NoMetadataSection1), SR.GetString(SR.SFxDocExt_NoMetadataSection2),
- SR.GetString(SR.SFxDocExt_NoMetadataSection3), SR.GetString(SR.SFxDocExt_NoMetadataSection4),
- SR.GetString(SR.SFxDocExt_NoMetadataConfigComment1), SR.GetString(SR.SFxDocExt_NoMetadataConfigComment2),
- SR.GetString(SR.SFxDocExt_NoMetadataConfigComment3), SR.GetString(SR.SFxDocExt_NoMetadataConfigComment4),
- SR.GetString(SR.SFxDocExt_NoMetadataSection5)
- ));
- writer.WriteEndElement(); //HTML
- }
- }
- class ServiceDescriptionMessage : ContentOnlyMessage
- {
- WsdlNS.ServiceDescription description;
- WriteFilter responseWriter;
- public ServiceDescriptionMessage(WsdlNS.ServiceDescription description, WriteFilter responseWriter)
- : base()
- {
- this.description = description;
- this.responseWriter = responseWriter;
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- this.responseWriter.Writer = writer;
- description.Write(this.responseWriter);
- }
- }
- class XmlSchemaMessage : ContentOnlyMessage
- {
- XmlSchema schema;
- WriteFilter responseWriter;
- public XmlSchemaMessage(XmlSchema schema, WriteFilter responseWriter)
- : base()
- {
- this.schema = schema;
- this.responseWriter = responseWriter;
- }
- protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
- {
- this.responseWriter.Writer = writer;
- schema.Write(responseWriter);
- }
- }
- #endregion //Helper Message implementations
- }
- internal abstract class WriteFilter : XmlDictionaryWriter
- {
- internal XmlWriter Writer;
- public abstract WriteFilter CloneWriteFilter();
- public override void Close()
- {
- this.Writer.Close();
- }
- public override void Flush()
- {
- this.Writer.Flush();
- }
- public override string LookupPrefix(string ns)
- {
- return this.Writer.LookupPrefix(ns);
- }
- public override void WriteBase64(byte[] buffer, int index, int count)
- {
- this.Writer.WriteBase64(buffer, index, count);
- }
- public override void WriteCData(string text)
- {
- this.Writer.WriteCData(text);
- }
- public override void WriteCharEntity(char ch)
- {
- this.Writer.WriteCharEntity(ch);
- }
- public override void WriteChars(char[] buffer, int index, int count)
- {
- this.Writer.WriteChars(buffer, index, count);
- }
- public override void WriteComment(string text)
- {
- this.Writer.WriteComment(text);
- }
- public override void WriteDocType(string name, string pubid, string sysid, string subset)
- {
- this.Writer.WriteDocType(name, pubid, sysid, subset);
- }
- public override void WriteEndAttribute()
- {
- this.Writer.WriteEndAttribute();
- }
- public override void WriteEndDocument()
- {
- this.Writer.WriteEndDocument();
- }
- public override void WriteEndElement()
- {
- this.Writer.WriteEndElement();
- }
- public override void WriteEntityRef(string name)
- {
- this.Writer.WriteEntityRef(name);
- }
- public override void WriteFullEndElement()
- {
- this.Writer.WriteFullEndElement();
- }
- public override void WriteProcessingInstruction(string name, string text)
- {
- this.Writer.WriteProcessingInstruction(name, text);
- }
- public override void WriteRaw(string data)
- {
- this.Writer.WriteRaw(data);
- }
- public override void WriteRaw(char[] buffer, int index, int count)
- {
- this.Writer.WriteRaw(buffer, index, count);
- }
- public override void WriteStartAttribute(string prefix, string localName, string ns)
- {
- this.Writer.WriteStartAttribute(prefix, localName, ns);
- }
- public override void WriteStartDocument(bool standalone)
- {
- this.Writer.WriteStartDocument(standalone);
- }
- public override void WriteStartDocument()
- {
- this.Writer.WriteStartDocument();
- }
- public override void WriteStartElement(string prefix, string localName, string ns)
- {
- this.Writer.WriteStartElement(prefix, localName, ns);
- }
- public override WriteState WriteState
- {
- get { return this.Writer.WriteState; }
- }
- public override void WriteString(string text)
- {
- this.Writer.WriteString(text);
- }
- public override void WriteSurrogateCharEntity(char lowChar, char highChar)
- {
- this.Writer.WriteSurrogateCharEntity(lowChar, highChar);
- }
- public override void WriteWhitespace(string ws)
- {
- this.Writer.WriteWhitespace(ws);
- }
- }
- class LocationUpdatingWriter : WriteFilter
- {
- readonly string oldValue;
- readonly string newValue;
- // passing null for newValue filters any string with oldValue as a prefix rather than replacing
- internal LocationUpdatingWriter(string oldValue, string newValue)
- {
- this.oldValue = oldValue;
- this.newValue = newValue;
- }
- public override WriteFilter CloneWriteFilter()
- {
- return new LocationUpdatingWriter(oldValue, newValue);
- }
- public override void WriteString(string text)
- {
- if (this.newValue != null)
- text = text.Replace(this.oldValue, this.newValue);
- else if (text.StartsWith(this.oldValue, StringComparison.Ordinal))
- text = String.Empty;
- base.WriteString(text);
- }
- }
- class DynamicAddressUpdateWriter : WriteFilter
- {
- readonly string oldHostName;
- readonly string newHostName;
- readonly string newBaseAddress;
- readonly bool removeBaseAddress;
- readonly string requestScheme;
- readonly int requestPort;
- readonly IDictionary<string, int> updatePortsByScheme;
- internal DynamicAddressUpdateWriter(Uri listenUri, string requestHost, int requestPort,
- IDictionary<string, int> updatePortsByScheme, bool removeBaseAddress)
- : this(listenUri.Host, requestHost, removeBaseAddress, listenUri.Scheme, requestPort, updatePortsByScheme)
- {
- this.newBaseAddress = UpdateUri(listenUri).ToString();
- }
- DynamicAddressUpdateWriter(string oldHostName, string newHostName, string newBaseAddress, bool removeBaseAddress, string requestScheme,
- int requestPort, IDictionary<string, int> updatePortsByScheme)
- : this(oldHostName, newHostName, removeBaseAddress, requestScheme, requestPort, updatePortsByScheme)
- {
- this.newBaseAddress = newBaseAddress;
- }
- DynamicAddressUpdateWriter(string oldHostName, string newHostName, bool removeBaseAddress, string requestScheme,
- int requestPort, IDictionary<string, int> updatePortsByScheme)
- {
- this.oldHostName = oldHostName;
- this.newHostName = newHostName;
- this.removeBaseAddress = removeBaseAddress;
- this.requestScheme = requestScheme;
- this.requestPort = requestPort;
- this.updatePortsByScheme = updatePortsByScheme;
- }
- public override WriteFilter CloneWriteFilter()
- {
- return new DynamicAddressUpdateWriter(this.oldHostName, this.newHostName, this.newBaseAddress, this.removeBaseAddress,
- this.requestScheme, this.requestPort, this.updatePortsByScheme);
- }
- public override void WriteString(string text)
- {
- Uri uri;
- if (this.removeBaseAddress &&
- text.StartsWith(ServiceMetadataExtension.BaseAddressPattern, StringComparison.Ordinal))
- {
- text = string.Empty;
- }
- else if (!this.removeBaseAddress &&
- text.Contains(ServiceMetadataExtension.BaseAddressPattern))
- {
- text = text.Replace(ServiceMetadataExtension.BaseAddressPattern, this.newBaseAddress);
- }
- else if (Uri.TryCreate(text, UriKind.Absolute, out uri))
- {
- Uri newUri = UpdateUri(uri);
- if (newUri != null)
- {
- text = newUri.ToString();
- }
- }
- base.WriteString(text);
- }
- public void UpdateUri(ref Uri uri, bool updateBaseAddressOnly = false)
- {
- Uri newUri = UpdateUri(uri, updateBaseAddressOnly);
- if (newUri != null)
- {
- uri = newUri;
- }
- }
- Uri UpdateUri(Uri uri, bool updateBaseAddressOnly = false)
- {
- // Ordinal comparison okay: we're filtering for auto-generated URIs which will
- // always be based off the listenURI, so always match in case
- if (uri.Host != oldHostName)
- {
- return null;
- }
- UriBuilder result = new UriBuilder(uri);
- result.Host = this.newHostName;
- if (!updateBaseAddressOnly)
- {
- int port;
- if (uri.Scheme == this.requestScheme)
- {
- port = requestPort;
- }
- else if (!this.updatePortsByScheme.TryGetValue(uri.Scheme, out port))
- {
- return null;
- }
- result.Port = port;
- }
- return result.Uri;
- }
- }
- }
- }
|