How to use WixSharp to install a website and associate an AppPool - msbuild

I am trying to find examples of how to use WixSharp (managed code interface to WiX) to install a website and associate an AppPool.
The steps I want to achieve are:
If the website exists in IIS 6, delete it.
If the AppPool exists in IIS 6, delete it.
Delete the application artifacts from the destination directory.
Copy the new application artifacts to the destination directory.
Create the AppPool.
Create the Website, linking it to the AppPool.
I have achieved this in MSBuild but that is not as useful as an MSI. Hence I am trying to "rewrite" the above in WixSharp syntax.
WixSharp apparently supports WIXIISExtension but Google has not yielded any examples yet.
How would I code the above in WixSharp?

I am using WIX for the same purpose. The application I am trying to deploy is around 300 MB and I need to create virtual directory for same, app pool, etc.
I think your requirement is same.
I would suggest WIX is really good for this. You can have screens asking user virtual directory name, Application Pool, etc.
WIX code works perfectly for IIS 5.1, 6, 7. For 7.5 you need to create a customaction. As such you can use wix to create virtual directory even for IIS 7.5 if IIS 6 compatibility mode is installed.
Uptill now I haven't faced any errors using WIX to deploy web applications.

Good question.
I am using WixSharp for my current project and I am really happy with it. It is awesome how you can avoid writing XML files by doing all with C# syntax. By the way, I don't think you are reinventing the wheel... you are speeding up the wheel with WixSharp.
As WixSharp version 1.9.6 doesn't provide creating a WebSite with an associated WebAppPool, I did it by creating a CustomWebSite.cs file. In this way, I created my Web Application using this code:
...
var project = new ManagedProject("My Project",
new InstallDir(#"c:\my_tool",
new Dir("my_frontend",
new Files($"{frontendDir}\\app\\*.*"),
new CustomWebSite("GateKeeper", "*:31515")
{
WebApplication = new CustomWebApplication("DemoApp")
{
WebAppPool = new WebAppPool("DemoApp", "ManagedPipelineMode=Integrated;Identity=applicationPoolIdentity"),
},
InstallWebSite = true
}
)
),
...
Here is my CustomWebSite.cs file that I only used to create one WebSite and I am sure it could be better:
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using WixSharp;
using WixSharp.CommonTasks;
using static WixSharp.WebSite;
namespace ToolBox.WixSharp
{
/// <summary>
/// Defines the WebSite element to be created associated to a Dir element.
/// </summary>
///<example>The following is an example of associating a CustomWebSite to a Dir element.
///
///<code>
/// var project =
/// new Project("My Product",
/// new Dir(#"%ProgramFiles%\My Company\My Product",
/// new Dir(#"some_dir",
/// new CustomWebSite("MyApp", "*:81")
/// {
/// WebApplication = new CustomWebApplication("DemoApp")
/// {
/// WebAppPool = new WebAppPool("DemoApp", "ManagedPipelineMode=Integrated;Identity=applicationPoolIdentity")
/// }
/// InstallWebSite = true
/// }
/// ...
///
/// Compiler.BuildMsi(project);
///</code>
///
/// This code will generate something like this:
///<code>
/// <Component Id="DemoApp_WebSite" Guid="a6896bba-1818-43e0-824f-9c585b3e366b" KeyPath="yes" Win64="yes">
/// <iis:WebSite Id = "DemoApp_WebSite" Description="DemoApp_WebSite" Directory="INSTALLDIR.some_dir">
/// <iis:WebAddress Id = "WebSite_Address1" IP="*" Port="31515" />
/// <iis:WebApplication Id = "DemoApp_WebApplication" Name="DemoApp" WebAppPool="DemoApp_AppPool"/>
/// </iis:WebSite>
/// <iis:WebAppPool Id = "DemoApp_AppPool" Name="DemoApp" ManagedPipelineMode="Integrated" Identity="applicationPoolIdentity" />
///
/// <CreateFolder />
/// <RemoveFolder Id = "INSTALLDIR.some_dir" On="uninstall" />
/// </Component>
/// </code>
/// </example>
public class CustomWebSite : WixEntity, IGenericEntity
{
/// <summary>
/// Indicates if the WebSite is to be installed (created on IIS) or existing WebSite should be used to install the corresponding
/// WebApplication. The default <see cref="InstallWebSite"/> value is <c>false</c>
/// <para>Developers should be aware of the WebSite installation model imposed by WiX/MSI and use <see cref="InstallWebSite"/> carefully.</para>
/// <para>If <see cref="InstallWebSite"/> value is set to <c>false</c> the parent WebApplication (<see cref="T:WixSharp.IISVirtualDir"/>)
/// will be installed in the brand new (freshly created) WebSite or in the existing one if a site with the same address/port combination already exists
/// on IIS). The undesirable side affect of this deployment scenario is that if the existing WebSite was used to install the WebApplication it will be
/// deleted on IIS during uninstallation even if this WebSite has other WebApplications installed.</para>
/// <para>The "safer" option is to set <see cref="InstallWebSite"/> value to <c>true</c> (default value). In this case the WebApplication will
/// be installed in an existing WebSite with matching address/port. If the match is not found the installation will fail. During the uninstallation
/// only installed WebApplication will be removed from IIS.</para>
/// </summary>
public bool InstallWebSite = false;
/// <summary>
/// Initializes a new instance of the <see cref="WebSite" /> class.
/// </summary>
public CustomWebSite()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomWebSite"/> class.
/// </summary>
/// <param name="description">The description of the web site (as it shows up in the IIS manager console).</param>
/// <param name="addressDefinition">The address definition.</param>
public CustomWebSite(string description, string addressDefinition)
{
this.Id = $"{description}_WebSite";
this.Description = description;
this.AddressesDefinition = addressDefinition;
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomWebSite"/> class.
/// </summary>
/// <param name="id">The id</param>
/// <param name="description">The description of the web site (as it shows up in the IIS manager console).</param>
/// <param name="addressDefinition">The address definition.</param>
public CustomWebSite(Id id, string description, string addressDefinition)
{
this.Id = id;
this.Description = description;
this.AddressesDefinition = addressDefinition;
}
internal void ProcessAddressesDefinition()
{
if (!AddressesDefinition.IsEmpty())
{
List<WebAddress> addressesToAdd = new List<WebAddress>();
foreach (string addressDef in AddressesDefinition.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
{
try
{
string[] tokens = addressDef.Split(":".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
string address = tokens[0];
string port = tokens[1];
if (tokens[1].ContainsWixConstants())
{
addressesToAdd.Add(new WebAddress { Address = address, AttributesDefinition = "Port=" + port });
}
else
{
addressesToAdd.Add(new WebAddress { Address = address, Port = Convert.ToInt32(port) });
}
}
catch (Exception e)
{
throw new Exception("Invalid AddressesDefinition", e);
}
}
this.addresses = addressesToAdd.ToArray();
}
}
/// <summary>
/// References a WebAppPool instance to use as the application pool for this application in IIS 6 applications.
/// </summary>
public string WebAppPool; //WebApplication element attribute
/// <summary>
/// Specification for auto-generating the <see cref="T:WebSite.WebAddresses"/> collection.
/// <para>If <see cref="AddressesDefinition"/> is specified, the existing content of <see cref="Addresses"/> will be ignored
/// and replaced with the auto-generated one at compile time.</para>
/// </summary>
/// <example>
/// <c>webSite.AddressesDefinition = "*:80;*90";</c> will be parsed and converted to an array of <see cref="T:WixSharp.WebSite.WebAddress"/> as follows:
/// <code>
/// ...
/// webSite.Addresses = new []
/// {
/// new WebSite.WebAddress
/// {
/// Address = "*",
/// Port = 80
/// },
/// new WebSite.WebAddress
/// {
/// Address = "*",
/// Port = 80
/// }
/// }
/// </code>
/// </example>
public string AddressesDefinition = "";
//// The iis:WebSite/#Directory attribute must be specified when the element has a Component as an ancestor..
//public string Directory = "";
/// <summary>
/// Reference to a WebApplication that is to be installed as part of this web site.
/// </summary>
public CustomWebApplication WebApplication = null;
/// <summary>
/// Collection of <see cref="T:WebSite.WebAddresses"/> associated with website.
/// <para>
/// The user specified values of <see cref="Addresses"/> will be ignored and replaced with the
/// auto-generated addresses if <see cref="AddressesDefinition"/> is specified either directly or via appropriate <see cref="WebSite"/> constructor.
/// </para>
/// </summary>
public WebAddress[] Addresses
{
get
{
ProcessAddressesDefinition();
return addresses;
}
set
{
addresses = value;
}
}
/// <summary>
/// This class defines WebAppPool WiX element. It is used to specify the application pool for this application in IIS 6 applications.
/// </summary>
public partial class CustomWebApplication : WixEntity
{
/// <summary>
/// References a WebAppPool instance to use as the application pool for this application in IIS 6 applications.
/// </summary>
public WebAppPool WebAppPool; //WebApplication element attribute
/// <summary>
/// Initializes a new instance of the <see cref="WebApplication"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="attributesDefinition">The attributes definition. This parameter is used to set encapsulated <see cref="T:WixSharp.WixEntity.AttributesDefinition"/>.</param>
public CustomWebApplication(string name, string attributesDefinition)
{
base.Id = $"{name}_WebApplication";
base.Name = name;
base.AttributesDefinition = attributesDefinition;
}
/// <summary>
/// Initializes a new instance of the <see cref="WebAppPool"/> class.
/// </summary>
/// <param name="name">The name.</param>
public CustomWebApplication(string name)
{
base.Id = $"{name}_WebApplication";
base.Name = name;
}
/// <summary>
/// Initializes a new instance of the <see cref="WebAppPool"/> class.
/// </summary>
public CustomWebApplication()
{
}
}
WebAddress[] addresses = new WebAddress[0];
/// <summary>
/// Primary key used to identify this particular entry.
/// </summary>
[Xml]
public new string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
/// <summary>
/// The value to set into the environment variable. If this attribute is not set, the environment variable is removed
/// during installation if it exists on the machine.
/// </summary>
[Xml]
public string Description;
/// <summary>
/// Defines the installation <see cref="Condition"/>, which is to be checked during the installation to
/// determine if the registry value should be created on the target system.
/// </summary>
public Condition Condition;
/// <summary>
/// Adds itself as an XML content into the WiX source being generated from the <see cref="WixSharp.Project"/>.
/// See 'Wix#/samples/Extensions' sample for the details on how to implement this interface correctly.
/// </summary>
/// <param name="context">The context.</param>
public void Process(ProcessingContext context)
{
// IIS namespace
XNamespace ns = WixExtension.IIs.ToXNamespace();
XElement component = this.CreateAndInsertParentComponent(context);
component.Add(this.ToXElement(ns + "WebSite"));
XElement webSiteElement = component.FindAll("WebSite")[0];
if (webSiteElement.Parent.Name == "Component" && webSiteElement.Parent.Parent.Name == "Directory")
{
// Add attributes for WebSite element
webSiteElement.AddAttributes($"Directory={webSiteElement.Parent.Parent.Attribute("Id").Value}");
}
if (Addresses != null)
{
int index = 1;
// Generates the XML fragment for WebAddress element
foreach (WebAddress address in Addresses)
{
webSiteElement.AddElement(new XElement(ns + "WebAddress",
new XAttribute("Id", $"WebSite_Address{index}"),
new XAttribute("IP", "*"),
new XAttribute("Port", address.Port)));
index++;
}
}
if (WebApplication != null)
{
// Generates the XML fragment for WebApplication element
XElement webApplicationElement = new XElement(ns + "WebApplication",
new XAttribute("Id", WebApplication.Id),
new XAttribute("Name", this.WebApplication.Name));
webSiteElement.AddElement(webApplicationElement);
if (WebApplication.WebAppPool != null)
{
WebApplication.WebAppPool.Id = $"{WebApplication.WebAppPool.Name}_WebAppPool";
webApplicationElement.SetAttribute($"WebAppPool={WebApplication.WebAppPool.Id}");
// Generates the XML fragment for WebAppPool element
webSiteElement.Parent.AddElement(new XElement(ns + "WebAppPool",
new XAttribute("Id", WebApplication.WebAppPool.Id),
new XAttribute("Name", WebApplication.WebAppPool.Name),
new XAttribute("ManagedPipelineMode", "Integrated"),
new XAttribute("Identity", "applicationPoolIdentity")));
}
}
if (Condition != null)
{
component.AddElement(new XElement("Condition", Condition.ToXValue())
.AddAttributes(Condition.Attributes));
}
}
}
}
You also have other way to solve your problem, by generating the WIX xml file following WebSite definitions and using XML injection as indicated in WixSharp IIS Sample with XMLInjection where you can subscribe to the WixSourceGenerated event.
project.WixSourceGenerated += Compiler_WixSourceGenerated;
Remember that WixSharp generates the Wix XML definitifion file, and you can modify this XML file after the WixSourceGenerated event.

Related

how to call my api qnamaker using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker nuget version 1.0.0

I am getting started with Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker nuget,
and I am trying to use QnAMakerClient() class to initialise a new instance of the QNAMakerClient class.
But this class takes abstract parameters :
public QnAMakerClient(ServiceClientCredentials credentials, params DelegatingHandler[] handlers).
I found some solution
https://csharp.hotexamples.com/examples/Microsoft.Rest/TokenCredentials/-/php-tokencredentials-class-examples.html which indicates how to get the credentials token.
As I am new to this concept, so I don't know how to get credentials token for my ServiceClientCredentials.
You can create your own TokenCredentials class which inherit from ServiceClientCredentials as shown below:
public class TokenCredentials : ServiceClientCredentials
{
/// <summary>
/// The bearer token type, as serialized in an http Authentication header.
/// </summary>
private const string BearerTokenType = "Bearer";
/// <summary>
/// Gets or sets secure token used to authenticate against Microsoft Azure API.
/// No anonymous requests are allowed.
/// </summary>
protected ITokenProvider TokenProvider { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="TokenCredentials"/>
/// class with the given 'Bearer' token.
/// </summary>
/// <param name="token">Valid JSON Web Token (JWT).</param>
public TokenCredentials(string token)
: this(token, BearerTokenType)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TokenCredentials"/>
/// class with the given token and token type.
/// </summary>
/// <param name="token">Valid JSON Web Token (JWT).</param>
/// <param name="tokenType">The token type of the given token.</param>
public TokenCredentials(string token, string tokenType)
: this(new StringTokenProvider(token, tokenType))
{
if (string.IsNullOrEmpty(token))
{
throw new ArgumentNullException("token");
}
if (string.IsNullOrEmpty(tokenType))
{
throw new ArgumentNullException("tokenType");
}
}
/// <summary>
/// Create an access token credentials object, given an interface to a token source.
/// </summary>
/// <param name="tokenProvider">The source of tokens for these credentials.</param>
public TokenCredentials(ITokenProvider tokenProvider)
{
if (tokenProvider == null)
{
throw new ArgumentNullException("tokenProvider");
}
this.TokenProvider = tokenProvider;
}
/// <summary>
/// Apply the credentials to the HTTP request.
/// </summary>
/// <param name="request">The HTTP request.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>
/// Task that will complete when processing has completed.
/// </returns>
public async override Task ProcessHttpRequestAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
if (TokenProvider == null)
{
throw new InvalidOperationException(Resources.TokenProviderCannotBeNull);
}
request.Headers.Authorization = await TokenProvider.GetAuthenticationHeaderAsync(cancellationToken);
await base.ProcessHttpRequestAsync(request, cancellationToken);
}
}
This is a good starting point for knowing more about QnAMaker.
Hope this helps!!!

Implementing bcrypt in .NET Core 2.0

I am new to developing with Core. I created a ASP.NET Core web application (MVC) in Visual Studio with Individual User accounts stored in app. I created a database for the app in SQL server, updated the connection string, and ran Update-Database in the NuGet console. I would like to override the password hashing function and instead use bcrypt to hash. I was hoping to use the BCrypt-Core, BCrypt.Net - Next, or Bcrypt-Official package. But I dont know where to go from there to ensure that the hashing is overridden when the password is generated and when the user logs in. My guess is that I need to override PasswordHasher but what methods do I need to override & what about when the user wants to log in? Any advice / suggestions / links to current implementations would be appreciated!
Create a class called BCryptPasswordHasher.cs
public class BCryptPasswordHasher<TUser> : PasswordHasher<TUser> where TUser : class
{
/// <summary>
/// Overrides instance of Microsoft.AspNetCore.Identity.PasswordHasher
/// </summary>
/// <param name="optionsAccessor"></param>
public BCryptPasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)
{
}
/// <summary>
/// Returns a hashed representation of the supplied password for the specified user.
/// </summary>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
public override string HashPassword(TUser user, string password)
{
return BCrypt.Net.BCrypt.HashPassword(password);
}
/// <summary>
/// Returns a Microsoft.AspNetCore.Identity.PasswordVerificationResult indicating
// the result of a password hash comparison.
/// </summary>
/// <param name="user"></param>
/// <param name="hashedPassword">The hash value for a user's stored password.</param>
/// <param name="providedPassword"> The password supplied for comparison.</param>
/// <returns></returns>
public override PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword)
{
if (hashedPassword == null) { throw new ArgumentNullException(nameof(hashedPassword)); }
if (providedPassword == null) { throw new ArgumentNullException(nameof(providedPassword)); }
if (BCrypt.Net.BCrypt.Verify(providedPassword, hashedPassword))
{
return PasswordVerificationResult.Success;
}
else
{
return PasswordVerificationResult.Failed;
}
}
}
In Startup.cs - BEFORE AddIdentity add
services.AddScoped<IPasswordHasher<ApplicationUser>, BCryptPasswordHasher<ApplicationUser>>();
Thanks to Andrew Lock for getting me 90% of the way there. https://andrewlock.net/migrating-passwords-in-asp-net-core-identity-with-a-custom-passwordhasher/

Workflow services scalability issue

I'm currently experiencing some issues with workflow services.
They work fine if I start 4, 5 in short sequence, but if I increase this value (starting from ~10) then I get the following exception:
This channel can no longer be used to send messages as the output session was auto-closed due to a server-initiated shutdown. Either disable auto-close by setting the DispatchRuntime.AutomaticInputSessionShutdown to false, or consider modifying the shutdown protocol with the remote server.
I think that the problem is in the way I create proxies. I use the following code to provide proxies, attempting to reuse existing ones:
public abstract class ProxyProvider<TService>
where TService : class
{
/// <summary>
/// Static reference to the current time provider.
/// </summary>
private static ProxyProvider<TService> current = DefaultProxyProvider.Instance;
private TService service;
/// <summary>
/// Gets or sets the current time provider.
/// </summary>
/// <value>
/// The current time provider.
/// </value>
public static ProxyProvider<TService> Current
{
get
{
return ProxyProvider<TService>.current;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
ProxyProvider<TService>.current = value;
}
}
/// <summary>
/// Resets to default.
/// </summary>
public static void ResetToDefault()
{
ProxyProvider<TService>.current = DefaultProxyProvider.Instance;
}
/// <summary>
/// Loads the proxy.
/// </summary>
/// <param name="forceNew">if set to <c>true</c> [force new].</param>
/// <returns>The instance of the proxy.</returns>
public virtual TService Provide(bool forceNew = false)
{
if (forceNew || !this.IsInstanceValid())
{
this.service = this.CreateInstance();
return this.service;
}
return this.service;
}
/// <summary>
/// Internals the load.
/// </summary>
/// <returns>The new created service.</returns>
protected abstract TService CreateInstance();
private bool IsInstanceValid()
{
var instance = this.service as ICommunicationObject;
if (instance == null)
{
return false;
}
return instance.State != CommunicationState.Faulted && instance.State != CommunicationState.Closed && instance.State != CommunicationState.Closing;
}
/// <summary>
/// Defines the default <see cref="ProxyProvider<TService>"/> which uses the System DateTime.UtcNow value.
/// </summary>
private sealed class DefaultProxyProvider : ProxyProvider<TService>
{
/// <summary>
/// Reference to the instance of the <see cref="ProxyProvider<TService>"/>.
/// </summary>
private static ProxyProvider<TService> instance;
/// <summary>
/// Gets the instance.
/// </summary>
public static ProxyProvider<TService> Instance
{
get
{
if (DefaultProxyProvider.instance == null)
{
DefaultProxyProvider.instance = new DefaultProxyProvider();
}
return DefaultProxyProvider.instance;
}
}
/// <summary>
/// Loads the specified force new.
/// </summary>
/// <returns>A non-disposed instance of the given service.</returns>
protected override TService CreateInstance()
{
var loadedService = Activator.CreateInstance<TService>();
return loadedService;
}
}
With an additional "lazy" provider:
public class CustomConstructorProxyProvider<TService> : ProxyProvider<TService>
where TService : class
{
private readonly Func<TService> constructor;
/// <summary>
/// Initializes a new instance of the <see cref="CustomConstructorProxyProvider<TService>"/> class.
/// </summary>
/// <param name="constructor">The constructor.</param>
public CustomConstructorProxyProvider(Func<TService> constructor)
{
this.constructor = constructor;
}
/// <summary>
/// Internals the load.
/// </summary>
/// <returns>The new created service.</returns>
protected override TService CreateInstance()
{
var service = this.constructor();
return service;
}
}
Used this way:
var proxy = ProxyProvider<IWorkflowService>.Current.Provide();
proxy.DoSomething();
Initialized like this:
ProxyProvider<IWorkflowService>.Current = new CustomConstructorProxyProvider<IWorkflowService>(() => new WorkflowServiceProxy("endpoint"));
Workflow services are hosted by IIS and I added the following throttling settings:
<serviceThrottling
maxConcurrentCalls="512"
maxConcurrentInstances="2147483647"
maxConcurrentSessions="1024"/>
which should be enough for my needs.
I hope that someone can help me configuring client and server to have achieve the desired scalability (a few hundreds started in sequence and running in parallel, using the WorkflowInstance sql store).
UPDATE:
I'm using NetTcpBinding for all services.
UPDATE 2:
All services are hosted and consumed by now locally.
Thanks
Francesco

How to intercept SOAP messages

As part of my venture into WCF I'm looking at message contracts and seeing how they affect the content of the SOAP message.
What would be really cool is if you could intercept the message and see how it is structured. How would I go about doing this please...
(So far I've looked at Wireshark (too 'low-level') and thought about Microsoft SOAP toolkit but this was retired by microsoft back in 2005)
When you installed .NET 3.5 or up, you should have the WCF Test Client on your machine somewhere (hidden deep inside a directory like C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ or something like that).
This tool allows you to connect to your WCF service, and you can call methods on it - and you can look at the XML request and response in all its beauty :-)
The other option would be to use something like the free version of SoapUI which is designed to test SOAP services and show request and response in XML
SoapUI is a great tool - but it's not WCF specific, it's just a "generic" SOAP/WSDL tool which works great against any SOAP service.
If you're not looking for "on-demand" capturing of requests and responses, but if you're more interested in having a trace of all requests and responses, you should investigate the WCF tracing features and setup them up as needed. You can capture all traffic into a *.svclog file on disk, and there's the WCF Service Trace Viewer (also free with WCF) to inspect those trace files.
I usually use Fiddler to inspect soap messages sent over http.
If you want to write a log record is try using TraceExtension class, below an example,
in this link you will find details about how to implement it, I use it and it worked very well
http://www.systemdeveloper.info/2013/11/trace-soap-requestresponse-xml-with.html
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services.Protocols;
using System.IO;
using System.Xml;
namespace PruebaServiciosNBC
{
class TraceExtension : SoapExtension
{
private Stream oldStream;
private Stream newStream;
private static XmlDocument xmlRequest;
/// <summary>
/// Gets the outgoing XML request sent to PayPal
/// </summary>
public static XmlDocument XmlRequest
{
get { return xmlRequest; }
}
private static XmlDocument xmlResponse;
/// <summary>
/// Gets the incoming XML response sent from PayPal
/// </summary>
public static XmlDocument XmlResponse
{
get { return xmlResponse; }
}
/// <summary>
/// Save the Stream representing the SOAP request
/// or SOAP response into a local memory buffer.
/// </summary>
/// <param name="stream">
/// <returns></returns>
public override Stream ChainStream(Stream stream)
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
/// <summary>
/// If the SoapMessageStage is such that the SoapRequest or
/// SoapResponse is still in the SOAP format to be sent or received,
/// save it to the xmlRequest or xmlResponse property.
/// </summary>
/// <param name="message">
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
xmlRequest = GetSoapEnvelope(newStream);
CopyStream(newStream, oldStream);
break;
case SoapMessageStage.BeforeDeserialize:
CopyStream(oldStream, newStream);
xmlResponse = GetSoapEnvelope(newStream);
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
/// <summary>
/// Returns the XML representation of the Soap Envelope in the supplied stream.
/// Resets the position of stream to zero.
/// </summary>
/// <param name="stream">
/// <returns></returns>
private XmlDocument GetSoapEnvelope(Stream stream)
{
XmlDocument xml = new XmlDocument();
stream.Position = 0;
StreamReader reader = new StreamReader(stream);
xml.LoadXml(reader.ReadToEnd());
stream.Position = 0;
return xml;
}
/// <summary>
/// Copies a stream.
/// </summary>
/// <param name="from">
/// <param name="to">
private void CopyStream(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.WriteLine(reader.ReadToEnd());
writer.Flush();
}
#region NoOp
/// <summary>
/// Included only because it must be implemented.
/// </summary>
/// <param name="methodInfo">
/// <param name="attribute">
/// <returns></returns>
public override object GetInitializer(LogicalMethodInfo methodInfo,
SoapExtensionAttribute attribute)
{
return null;
}
/// <summary>
/// Included only because it must be implemented.
/// </summary>
/// <param name="WebServiceType">
/// <returns></returns>
public override object GetInitializer(Type WebServiceType)
{
return null;
}
/// <summary>
/// Included only because it must be implemented.
/// </summary>
/// <param name="initializer">
public override void Initialize(object initializer)
{
}
#endregion NoOp
}
}

WCF Routing with custom filter: No matching MessageFilter was found for the given message

Im writing a routing service for some WCF services. I want to use a custom message filter that filters out my message.
Here is my custom message filter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Dispatcher;
using System.IO;
using System.Xml;
namespace AMA_ISE.Router.MessageFilter
{
/// <summary>
/// This message filter implements the custom filtering for the routing services to re-route the incoming messages
/// correctly.
/// </summary>
public class CustomMessageFilter : System.ServiceModel.Dispatcher.MessageFilter
{
/// <summary>
/// The sales channel for which the filter is configured.
/// </summary>
private string _salesChannel;
/// <summary>
/// The service, for which the filter is configured.
/// </summary>
private string _service;
/// <summary>
/// Initializes a new instance of the <see cref="CustomMessageFilter"/> class.
/// </summary>
/// <param name="messageData">The message data.</param>
public CustomMessageFilter(string messageData)
{
var splitted = messageData.Split("|".ToCharArray());
_salesChannel = splitted[0];
_service = splitted[1];
}
/// <summary>
/// When overridden in a derived class, tests whether a message satisfies the filter criteria. The body cannot be examined.
/// </summary>
/// <param name="message">The <see cref="T:System.ServiceModel.Channels.Message"/> object to test.</param>
/// <returns>
/// true if the <see cref="T:System.ServiceModel.Channels.Message"/> object satisfies the filter criteria; otherwise, false.
/// </returns>
public override bool Match(System.ServiceModel.Channels.Message message)
{
return true;
}
/// <summary>
/// When overridden in a derived class, tests whether a buffered message satisfies the criteria of a filter.
/// </summary>
/// <param name="buffer">The <see cref="T:System.ServiceModel.Channels.MessageBuffer"/> object to test.</param>
/// <returns>
/// true if the <see cref="T:System.ServiceModel.Channels.MessageBuffer"/> object satisfies the filter criteria; otherwise, false.
/// </returns>
public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
{
using (var memStream = new MemoryStream())
{
var msgText = GetMessageEnvelope(buffer);
// check sales channel
if (GetValueByTagName(msgText, "SalesChannel") != _salesChannel)
return false;
// check the requested service
var toValue = GetValueByTagName(msgText, "To");
File.AppendAllText("C:\\temp\\filter.txt", toValue + " - " + _service + " - " + toValue.Trim().EndsWith(_service) + "\r\n");
return toValue.Trim().EndsWith(_service);
}
}
/// <summary>
/// Gets the message envelope.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns></returns>
private static string GetMessageEnvelope(System.ServiceModel.Channels.MessageBuffer buffer)
{
using (MemoryStream memStream = new MemoryStream())
{
var msg = buffer.CreateMessage();
XmlDictionaryWriter xmlDictWriter =
XmlDictionaryWriter.CreateTextWriter(memStream);
msg.WriteMessage(xmlDictWriter);
xmlDictWriter.Flush();
memStream.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(memStream);
var text = reader.ReadToEnd();
return text;
}
}
/// <summary>
/// Gets the name of the value by tag.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="tagName">Name of the tag which content we are searching for.</param>
/// <returns></returns>
private static string GetValueByTagName(string text, string tagName)
{
var txt = text.Substring(text.IndexOf("<" + tagName+" ") + 1);
txt = txt.Substring(txt.IndexOf(">") + 1);
return txt.Substring(0, txt.IndexOf("<"));
}
}
}
As you can see, Im writing some information in a log file located in c:\temp. I expect to match the current filter when Match() returns true; that is not happening. When using this filter, I have the following text file:
http://localhost/AMA-ISE.Router/BookingService.svc - RetrieveService.svc - False
http://localhost/AMA-ISE.Router/BookingService.svc - CancelService.svc - False
http://localhost/AMA-ISE.Router/BookingService.svc - BookingService.svc - True
http://localhost/AMA-ISE.Router/BookingService.svc - AvailService.svc - False
http://localhost/AMA-ISE.Router/BookingService.svc - RebookingService.svc - False
That Looks nice, the BookingService.svc does fits and Match() will give true back. But it doesnt work. When I now use the routing service, I get the following error:
Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: No matching MessageFilter was found for the given Message.
Whats wrong? Have I misunderstood something with the message filters? Please help me!
Michael Baarz
not all XML are formed in the same manner. Sometimes tags containing namespaces directly and sometimes the namespace is prefixed and used. That was why my method has not worked. Here is the working code for a custom filter manager (maybe someone is interested in):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Dispatcher;
using System.IO;
using System.Xml;
using System.Xml.Linq;
namespace AMA_ISE.Router.MessageFilter
{
/// <summary>
/// This message filter implements the custom filtering for the routing services to re-route the incoming messages
/// correctly.
/// </summary>
public class CustomMessageFilter : System.ServiceModel.Dispatcher.MessageFilter
{
/// <summary>
/// The sales channel for which the filter is configured.
/// </summary>
private string _salesChannel;
/// <summary>
/// The service, for which the filter is configured.
/// </summary>
private string _service;
/// <summary>
/// Initializes a new instance of the <see cref="CustomMessageFilter"/> class.
/// </summary>
/// <param name="messageData">The message data.</param>
public CustomMessageFilter(string messageData)
{
var splitted = messageData.Split("|".ToCharArray());
_salesChannel = splitted[0];
_service = splitted[1];
}
/// <summary>
/// When overridden in a derived class, tests whether a message satisfies the filter criteria. The body cannot be examined.
/// </summary>
/// <param name="message">The <see cref="T:System.ServiceModel.Channels.Message"/> object to test.</param>
/// <returns>
/// true if the <see cref="T:System.ServiceModel.Channels.Message"/> object satisfies the filter criteria; otherwise, false.
/// </returns>
public override bool Match(System.ServiceModel.Channels.Message message)
{
return true;
}
static int counter = 0;
/// <summary>
/// When overridden in a derived class, tests whether a buffered message satisfies the criteria of a filter.
/// </summary>
/// <param name="buffer">The <see cref="T:System.ServiceModel.Channels.MessageBuffer"/> object to test.</param>
/// <returns>
/// true if the <see cref="T:System.ServiceModel.Channels.MessageBuffer"/> object satisfies the filter criteria; otherwise, false.
/// </returns>
public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
{
using (var memStream = new MemoryStream())
{
var document = GetMessageEnvelope(buffer);
var salesChannel = GetValueOfNamedNode(document.Root, "SalesChannel");
var toService = GetValueOfNamedNode(document.Root, "To");
return salesChannel == _salesChannel && toService.EndsWith(_service);
}
}
private static string GetValueOfNamedNode(XElement element, string findName)
{
if (element.Name.LocalName == findName)
return element.Value;
foreach (var ele in element.Elements())
{
var foundValue = GetValueOfNamedNode(ele, findName);
if (foundValue != null)
return foundValue;
}
return null;
}
/// <summary>
/// Gets the message envelope.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns></returns>
private static XDocument GetMessageEnvelope(System.ServiceModel.Channels.MessageBuffer buffer)
{
using (MemoryStream memStream = new MemoryStream())
{
var msg = buffer.CreateMessage();
XmlDictionaryWriter xmlDictWriter =
XmlDictionaryWriter.CreateTextWriter(memStream);
msg.WriteMessage(xmlDictWriter);
xmlDictWriter.Flush();
memStream.Seek(0, SeekOrigin.Begin);
var doc = XDocument.Load(memStream);
return doc;
}
}
}
}