Code:
/ WCF / WCF / 3.5.30729.1 / untmp / Orcas / SP / ndp / cdf / src / WCF / ServiceModel / System / ServiceModel / ClientBase.cs / 1 / ClientBase.cs
//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------- using System.ServiceModel.Diagnostics; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.ComponentModel; namespace System.ServiceModel { public abstract class ClientBase: ICommunicationObject, IDisposable where TChannel : class { TChannel channel = null; ChannelFactoryRef channelFactoryRef; EndpointTrait endpointTrait; // Determine whether the proxy can share factory with others. It is false only if the public getters // are invoked. bool canShareFactory = true; // Determine whether the proxy is currently holding a cached factory bool useCachedFactory; // Determine whether we have locked down sharing for this proxy. This is turned on only when the channel // is created. bool sharingFinalized; // Determine whether the ChannelFactoryRef has been released. We should release it only once per proxy bool channelFactoryRefReleased; // Determine whether we have released the last ref count of the ChannelFactory so that we could abort it when it was closing. bool releasedLastRef; object syncRoot = new object(); object finalizeLock = new object(); // Cache at most 32 ChannelFactories static ChannelFactoryRefCache factoryRefCache = new ChannelFactoryRefCache (32); static object staticLock = new object(); static AsyncCallback onAsyncCallCompleted = DiagnosticUtility.Utility.ThunkCallback(new AsyncCallback(OnAsyncCallCompleted)); // IMPORTANT: any changes to the set of protected .ctors of this class need to be reflected // in ServiceContractGenerator.cs as well. protected ClientBase() { endpointTrait = new EndpointTrait ("*", null, null); InitializeChannelFactoryRef(); } protected ClientBase(string endpointConfigurationName) { if (endpointConfigurationName == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName"); endpointTrait = new EndpointTrait (endpointConfigurationName, null, null); InitializeChannelFactoryRef(); } protected ClientBase(string endpointConfigurationName, string remoteAddress) { if (endpointConfigurationName == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName"); if (remoteAddress == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress"); endpointTrait = new EndpointTrait (endpointConfigurationName, new EndpointAddress(remoteAddress), null); InitializeChannelFactoryRef(); } protected ClientBase(string endpointConfigurationName, EndpointAddress remoteAddress) { if (endpointConfigurationName == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName"); if (remoteAddress == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress"); endpointTrait = new EndpointTrait (endpointConfigurationName, remoteAddress, null); InitializeChannelFactoryRef(); } protected ClientBase(Binding binding, EndpointAddress remoteAddress) { if (binding == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("binding"); if (remoteAddress == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress"); channelFactoryRef = new ChannelFactoryRef (new ChannelFactory (binding, remoteAddress)); channelFactoryRef.ChannelFactory.TraceOpenAndClose = false; TryDisableSharing(); } protected ClientBase(InstanceContext callbackInstance) { if (callbackInstance == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance"); endpointTrait = new EndpointTrait ("*", null, callbackInstance); InitializeChannelFactoryRef(); } protected ClientBase(InstanceContext callbackInstance, string endpointConfigurationName) { if (callbackInstance == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance"); if (endpointConfigurationName == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName"); endpointTrait = new EndpointTrait (endpointConfigurationName, null, callbackInstance); InitializeChannelFactoryRef(); } protected ClientBase(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) { if (callbackInstance == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance"); if (endpointConfigurationName == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName"); if (remoteAddress == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress"); endpointTrait = new EndpointTrait (endpointConfigurationName, new EndpointAddress(remoteAddress), callbackInstance); InitializeChannelFactoryRef(); } protected ClientBase(InstanceContext callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress) { if (callbackInstance == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance"); if (endpointConfigurationName == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName"); if (remoteAddress == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress"); endpointTrait = new EndpointTrait (endpointConfigurationName, remoteAddress, callbackInstance); InitializeChannelFactoryRef(); } protected ClientBase(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress) { if (callbackInstance == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance"); if (binding == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("binding"); if (remoteAddress == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress"); channelFactoryRef = new ChannelFactoryRef ( new DuplexChannelFactory (callbackInstance, binding, remoteAddress)); channelFactoryRef.ChannelFactory.TraceOpenAndClose = false; TryDisableSharing(); } protected T GetDefaultValueForInitialization () { return default(T); } object ThisLock { get { return syncRoot; } } protected TChannel Channel { get { // created on demand, so that Mort can modify .Endpoint before calling methods on the client if (this.channel == null) { lock (ThisLock) { if (this.channel == null) { using (ServiceModelActivity activity = DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.CreateBoundedActivity() : null) { if (DiagnosticUtility.ShouldUseActivity) { ServiceModelActivity.Start(activity, SR.GetString(SR.ActivityOpenClientBase, typeof(TChannel).FullName), ActivityType.OpenClient); } if (this.useCachedFactory) { try { CreateChannelInternal(); } #pragma warning suppress 56500 // covered by FxCOP catch (Exception ex) { if (this.useCachedFactory && (ex is CommunicationException || ex is ObjectDisposedException || ex is TimeoutException)) { DiagnosticUtility.ExceptionUtility.TraceHandledException(ex, TraceEventType.Warning); InvalidateCacheAndCreateChannel(); } else { #pragma warning suppress 56503 // [....], We throw only for unknown exceptions. throw; } } } else { CreateChannelInternal(); } } } } } return channel; } } public ChannelFactory ChannelFactory { get { TryDisableSharing(); return GetChannelFactory(); } } public ClientCredentials ClientCredentials { get { TryDisableSharing(); return this.ChannelFactory.Credentials; } } public CommunicationState State { get { IChannel channel = (IChannel)this.channel; if (channel != null) { return channel.State; } else return CommunicationState.Created; } } public IClientChannel InnerChannel { get { return (IClientChannel)Channel; } } public ServiceEndpoint Endpoint { get { TryDisableSharing(); return GetChannelFactory().Endpoint; } } public void Open() { ((ICommunicationObject)this).Open(GetChannelFactory().InternalOpenTimeout); } public void Abort() { IChannel channel = (IChannel)this.channel; if (channel != null) { channel.Abort(); } if (!channelFactoryRefReleased) { lock (staticLock) { if (!channelFactoryRefReleased) { if (this.channelFactoryRef.Release()) { this.releasedLastRef = true; } channelFactoryRefReleased = true; } } } // Abort the ChannelFactory if we released the last one. We should be able to abort it when another thread is closing it. if (this.releasedLastRef) { this.channelFactoryRef.Abort(); } } public void Close() { ((ICommunicationObject)this).Close(GetChannelFactory().InternalCloseTimeout); } public void DisplayInitializationUI() { ((IClientChannel)this.InnerChannel).DisplayInitializationUI(); } void CreateChannelInternal() { try { this.channel = this.CreateChannel(); if (this.sharingFinalized) { if (this.canShareFactory && !this.useCachedFactory) { // It is OK to add ChannelFactory to the cache now. TryAddChannelFactoryToCache(); } } } finally { if (!this.sharingFinalized) { // this.CreateChannel() is not called. For safety, we disable sharing. TryDisableSharing(); } } } protected virtual TChannel CreateChannel() { if (this.sharingFinalized) return GetChannelFactory().CreateChannel(); lock (this.finalizeLock) { this.sharingFinalized = true; return GetChannelFactory().CreateChannel(); } } void IDisposable.Dispose() { this.Close(); } void ICommunicationObject.Open(TimeSpan timeout) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); if (!this.useCachedFactory) { GetChannelFactory().Open(timeoutHelper.RemainingTime()); } this.InnerChannel.Open(timeoutHelper.RemainingTime()); } void ICommunicationObject.Close(TimeSpan timeout) { using (ServiceModelActivity activity = DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.CreateBoundedActivity() : null) { if (DiagnosticUtility.ShouldUseActivity) { ServiceModelActivity.Start(activity, SR.GetString(SR.ActivityCloseClientBase, typeof(TChannel).FullName), ActivityType.Close); } TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); if (this.channel != null) InnerChannel.Close(timeoutHelper.RemainingTime()); if (!channelFactoryRefReleased) { lock (staticLock) { if (!channelFactoryRefReleased) { if (this.channelFactoryRef.Release()) { this.releasedLastRef = true; } this.channelFactoryRefReleased = true; } } // Close the factory outside of the lock so that we can abort from a different thread. if (this.releasedLastRef) { if (this.useCachedFactory) { this.channelFactoryRef.Abort(); } else { this.channelFactoryRef.Close(timeoutHelper.RemainingTime()); } } } } } event EventHandler ICommunicationObject.Closed { add { this.InnerChannel.Closed += value; } remove { this.InnerChannel.Closed -= value; } } event EventHandler ICommunicationObject.Closing { add { this.InnerChannel.Closing += value; } remove { this.InnerChannel.Closing -= value; } } event EventHandler ICommunicationObject.Faulted { add { this.InnerChannel.Faulted += value; } remove { this.InnerChannel.Faulted -= value; } } event EventHandler ICommunicationObject.Opened { add { this.InnerChannel.Opened += value; } remove { this.InnerChannel.Opened -= value; } } event EventHandler ICommunicationObject.Opening { add { this.InnerChannel.Opening += value; } remove { this.InnerChannel.Opening -= value; } } IAsyncResult ICommunicationObject.BeginClose(AsyncCallback callback, object state) { return ((ICommunicationObject)this).BeginClose(GetChannelFactory().InternalCloseTimeout, callback, state); } IAsyncResult ICommunicationObject.BeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return new ChainedAsyncResult(timeout, callback, state, BeginChannelClose, EndChannelClose, BeginFactoryClose, EndFactoryClose); } void ICommunicationObject.EndClose(IAsyncResult result) { ChainedAsyncResult.End(result); } IAsyncResult ICommunicationObject.BeginOpen(AsyncCallback callback, object state) { return ((ICommunicationObject)this).BeginOpen(GetChannelFactory().InternalOpenTimeout, callback, state); } IAsyncResult ICommunicationObject.BeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { return new ChainedAsyncResult(timeout, callback, state, BeginFactoryOpen, EndFactoryOpen, BeginChannelOpen, EndChannelOpen); } void ICommunicationObject.EndOpen(IAsyncResult result) { ChainedAsyncResult.End(result); } //ChainedAsyncResult methods for opening and closing ChannelFactory internal IAsyncResult BeginFactoryOpen(TimeSpan timeout, AsyncCallback callback, object state) { if (this.useCachedFactory) { return new CompletedAsyncResult(callback, state); } else { return GetChannelFactory().BeginOpen(timeout, callback, state); } } internal void EndFactoryOpen(IAsyncResult result) { if (this.useCachedFactory) { CompletedAsyncResult.End(result); } else { GetChannelFactory().EndOpen(result); } } internal IAsyncResult BeginChannelOpen(TimeSpan timeout, AsyncCallback callback, object state) { return this.InnerChannel.BeginOpen(timeout, callback, state); } internal void EndChannelOpen(IAsyncResult result) { this.InnerChannel.EndOpen(result); } internal IAsyncResult BeginFactoryClose(TimeSpan timeout, AsyncCallback callback, object state) { if (this.useCachedFactory) { return new CompletedAsyncResult(callback, state); } else { return GetChannelFactory().BeginClose(timeout, callback, state); } } internal void EndFactoryClose(IAsyncResult result) { if (typeof(CompletedAsyncResult).IsAssignableFrom(result.GetType())) { CompletedAsyncResult.End(result); } else { GetChannelFactory().EndClose(result); } } internal IAsyncResult BeginChannelClose(TimeSpan timeout, AsyncCallback callback, object state) { if (this.channel != null) { return this.InnerChannel.BeginClose(timeout, callback, state); } else { return new CompletedAsyncResult(callback, state); } } internal void EndChannelClose(IAsyncResult result) { if (typeof(CompletedAsyncResult).IsAssignableFrom(result.GetType())) { CompletedAsyncResult.End(result); } else { this.InnerChannel.EndClose(result); } } ChannelFactory GetChannelFactory() { return this.channelFactoryRef.ChannelFactory; } void InitializeChannelFactoryRef() { DiagnosticUtility.DebugAssert(this.channelFactoryRef == null, "The channelFactory should have never been assigned"); DiagnosticUtility.DebugAssert(this.canShareFactory, "GetChannelFactoryFromCache can be called only when canShareFactory is true"); lock (staticLock) { ChannelFactoryRef factoryRef; if (factoryRefCache.TryGetValue(this.endpointTrait, out factoryRef)) { if (factoryRef.ChannelFactory.State != CommunicationState.Opened) { // Remove the bad ChannelFactory. factoryRefCache.Remove(this.endpointTrait); } else { this.channelFactoryRef = factoryRef; this.channelFactoryRef.AddRef(); useCachedFactory = true; return; } } } if (this.channelFactoryRef == null) { // Creating the ChannelFactory at initial time to catch configuration exception earlier. this.channelFactoryRef = CreateChannelFactoryRef(this.endpointTrait); } } static ChannelFactoryRef CreateChannelFactoryRef(EndpointTrait endpointTrait) { DiagnosticUtility.DebugAssert(endpointTrait != null, "The endpointTrait should not be null when the factory can be shared."); ChannelFactory channelFactory = endpointTrait.CreateChannelFactory(); channelFactory.TraceOpenAndClose = false; return new ChannelFactoryRef (channelFactory); } // Once the channel is created, we can't disable caching. void TryDisableSharing() { if (this.sharingFinalized) return; lock (this.finalizeLock) { if (this.sharingFinalized) return; this.canShareFactory = false; this.sharingFinalized = true; if (this.useCachedFactory) { ChannelFactoryRef pendingFactoryRef = this.channelFactoryRef; this.channelFactoryRef = CreateChannelFactoryRef(this.endpointTrait); this.useCachedFactory = false; lock (staticLock) { if (!pendingFactoryRef.Release()) { pendingFactoryRef = null; } } if (pendingFactoryRef != null) pendingFactoryRef.Abort(); } } } void TryAddChannelFactoryToCache() { DiagnosticUtility.DebugAssert(this.canShareFactory, "This should be called only when this proxy can share ChannelFactory."); DiagnosticUtility.DebugAssert(this.channelFactoryRef.ChannelFactory.State == CommunicationState.Opened, "The ChannelFactory must be in Opened state for caching."); // Lock the cache and add the item to synchronize with lookup. lock (staticLock) { ChannelFactoryRef cfRef; if (!factoryRefCache.TryGetValue(this.endpointTrait, out cfRef)) { // Increment the ref count before adding to the cache. this.channelFactoryRef.AddRef(); factoryRefCache.Add(this.endpointTrait, this.channelFactoryRef); this.useCachedFactory = true; } } } // NOTE: This should be called inside ThisLock void InvalidateCacheAndCreateChannel() { RemoveFactoryFromCache(); TryDisableSharing(); CreateChannelInternal(); } void RemoveFactoryFromCache() { lock (staticLock) { ChannelFactoryRef factoryRef; if (factoryRefCache.TryGetValue(this.endpointTrait, out factoryRef)) { if (object.ReferenceEquals(this.channelFactoryRef, factoryRef)) { factoryRefCache.Remove(this.endpointTrait); } } } } // WARNING: changes in the signature/name of the following delegates must be applied to the // ClientClassGenerator.cs as well, otherwise the ClientClassGenerator would generate wrong code. protected delegate IAsyncResult BeginOperationDelegate(object[] inValues, AsyncCallback asyncCallback, object state); protected delegate object[] EndOperationDelegate(IAsyncResult result); // WARNING: Any changes in the signature/name of the following type and its ctor must be applied to the // ClientClassGenerator.cs as well, otherwise the ClientClassGenerator would generate wrong code. protected class InvokeAsyncCompletedEventArgs : AsyncCompletedEventArgs { object[] results; internal InvokeAsyncCompletedEventArgs(object[] results, Exception error, bool cancelled, object userState) : base(error, cancelled, userState) { this.results = results; } public object[] Results { get { return this.results; } } } // WARNING: Any changes in the signature/name of the following method ctor must be applied to the // ClientClassGenerator.cs as well, otherwise the ClientClassGenerator would generate wrong code. protected void InvokeAsync(BeginOperationDelegate beginOperationDelegate, object[] inValues, EndOperationDelegate endOperationDelegate, SendOrPostCallback operationCompletedCallback, object userState) { if (beginOperationDelegate == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("beginOperationDelegate"); } if (endOperationDelegate == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endOperationDelegate"); } AsyncOperation asyncOperation = AsyncOperationManager.CreateOperation(userState); AsyncOperationContext context = new AsyncOperationContext(asyncOperation, endOperationDelegate, operationCompletedCallback); Exception error = null; object[] results = null; IAsyncResult result = null; try { result = beginOperationDelegate(inValues, onAsyncCallCompleted, context); if (result.CompletedSynchronously) { results = endOperationDelegate(result); } } catch (Exception e) { if (DiagnosticUtility.IsFatal(e)) { throw; } error = e; } if (error != null || result.CompletedSynchronously) /* result cannot be null if error == null */ { CompleteAsyncCall(context, results, error); } } static void OnAsyncCallCompleted(IAsyncResult result) { if (result.CompletedSynchronously) { return; } AsyncOperationContext context = (AsyncOperationContext)result.AsyncState; Exception error = null; object[] results = null; try { results = context.EndDelegate(result); } catch (Exception e) { if (DiagnosticUtility.IsFatal(e)) { throw; } error = e; } CompleteAsyncCall(context, results, error); } static void CompleteAsyncCall(AsyncOperationContext context, object[] results, Exception error) { if (context.CompletionCallback != null) { InvokeAsyncCompletedEventArgs e = new InvokeAsyncCompletedEventArgs(results, error, false, context.AsyncOperation.UserSuppliedState); context.AsyncOperation.PostOperationCompleted(context.CompletionCallback, e); } else { context.AsyncOperation.OperationCompleted(); } } class AsyncOperationContext { AsyncOperation asyncOperation; EndOperationDelegate endDelegate; SendOrPostCallback completionCallback; internal AsyncOperationContext(AsyncOperation asyncOperation, EndOperationDelegate endDelegate, SendOrPostCallback completionCallback) { this.asyncOperation = asyncOperation; this.endDelegate = endDelegate; this.completionCallback = completionCallback; } internal AsyncOperation AsyncOperation { get { return this.asyncOperation; } } internal EndOperationDelegate EndDelegate { get { return this.endDelegate; } } internal SendOrPostCallback CompletionCallback { get { return this.completionCallback; } } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- KeyGesture.cs
- DataSourceGeneratorException.cs
- ZipIOCentralDirectoryBlock.cs
- IBuiltInEvidence.cs
- PointCollection.cs
- DesignerExtenders.cs
- XPathSingletonIterator.cs
- DesignerUtility.cs
- InstancePersistenceCommandException.cs
- MonitoringDescriptionAttribute.cs
- SoapAttributeOverrides.cs
- DoubleUtil.cs
- BoolExpression.cs
- NavigationProperty.cs
- EDesignUtil.cs
- QilGenerator.cs
- HtmlInputCheckBox.cs
- ConfigurationElementCollection.cs
- PageHandlerFactory.cs
- Classification.cs
- StickyNoteContentControl.cs
- JsonObjectDataContract.cs
- ResolvedKeyFrameEntry.cs
- WinEventTracker.cs
- CTreeGenerator.cs
- TextParagraphProperties.cs
- UIElementHelper.cs
- HttpModulesSection.cs
- QueryCacheEntry.cs
- QueryContinueDragEvent.cs
- DataViewListener.cs
- ConfigsHelper.cs
- DataGridViewSelectedRowCollection.cs
- GridView.cs
- PointAnimation.cs
- GeneralTransform3DGroup.cs
- BindingExpressionUncommonField.cs
- ExternalFile.cs
- DataListDesigner.cs
- XXXInfos.cs
- WebControlAdapter.cs
- CodeSubDirectoriesCollection.cs
- InvalidProgramException.cs
- DrawingContextDrawingContextWalker.cs
- UnionExpr.cs
- FlowDecisionLabelFeature.cs
- ValidationRuleCollection.cs
- BamlCollectionHolder.cs
- ReadWriteSpinLock.cs
- TypedRowGenerator.cs
- ToolStripSeparatorRenderEventArgs.cs
- RtType.cs
- DelayedRegex.cs
- ColumnWidthChangedEvent.cs
- ProfessionalColors.cs
- RtfFormatStack.cs
- OwnerDrawPropertyBag.cs
- WebRequestModuleElement.cs
- SharedPerformanceCounter.cs
- EntityDataSourceView.cs
- CodePropertyReferenceExpression.cs
- ConsumerConnectionPoint.cs
- XmlSchemaInfo.cs
- TemplateComponentConnector.cs
- WinFormsSpinner.cs
- RangeValidator.cs
- UITypeEditor.cs
- Missing.cs
- DataGrid.cs
- RTLAwareMessageBox.cs
- Grammar.cs
- UIElementAutomationPeer.cs
- DefaultProxySection.cs
- DragEvent.cs
- PeerUnsafeNativeCryptMethods.cs
- ScrollProperties.cs
- ProcessModelSection.cs
- DiscoveryReferences.cs
- ConfigXmlSignificantWhitespace.cs
- Tokenizer.cs
- FileDialog.cs
- SafeSerializationManager.cs
- BitmapEffectInput.cs
- Visitor.cs
- DataGridAutoFormat.cs
- ThreadNeutralSemaphore.cs
- WebSysDisplayNameAttribute.cs
- XamlRtfConverter.cs
- Metafile.cs
- ConditionalBranch.cs
- XmlDocumentFragment.cs
- DeflateEmulationStream.cs
- OpenTypeLayout.cs
- SQLBytes.cs
- AlphaSortedEnumConverter.cs
- MenuItemStyle.cs
- XmlAttributeOverrides.cs
- FileLevelControlBuilderAttribute.cs
- HttpRequest.cs
- TemplateAction.cs