Auto detect URL, phone number, email in TextBlock - xaml

I would like to automatically highlight URL, Email and phone number in UWP. It is possible in Android but it seems this features has been forgotten by Microsoft.
In my use case, I get the text from a web service, so I don't know the text format which is a user text input on the web platform.

The platform is not supporting this feature (yet). When I've to do the same thing, I've ended with my own solution which is to:
create an attached property receiving the text to format
use some regex to extract the URL, phone numbers, email addresses from it
generate an Inlines collection which I'm injecting to the attached TextBlock control
The regex used are covering a lot of cases but some edge cases can be still missing.
It is used this way:
<TextBlock uwpext:TextBlock.InteractiveText="Here is a link www.bing.com to send to a#a.com or 0000000000" />
The attached property code:
// -------------------------------------------------------------------------------------------
/// <summary>
/// The regex to detect the URL from the text content
/// It comes from https://gist.github.com/gruber/249502 (http://daringfireball.net/2010/07/improved_regex_for_matching_urls)
/// </summary>
private static readonly Regex UrlRegex = new Regex(#"(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'"".,<>?«»“”‘’]))", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The regex to detect the email addresses
/// It comes from https://msdn.microsoft.com/en-us/library/01escwtf.aspx
/// </summary>
private static readonly Regex EmailRegex = new Regex(#"(?("")("".+?(?<!\\)""#)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])#))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The regex to detect the phone numbers from the raw message
/// </summary>
private static readonly Regex PhoneRegex = new Regex(#"\+?[\d\-\(\)\. ]{5,}", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The default prefix to use to convert a relative URI to an absolute URI
/// The Windows RunTime is only working with absolute URI
/// </summary>
private const string RelativeUriDefaultPrefix = "http://";
// -------------------------------------------------------------------------------------------
/// <summary>
/// The dependency property to generate an interactive text in a text block.
/// When setting this property, we will parse the value and transform the hyperlink or the email address to interactive fields that the user can interact width.
/// The raw text will be parsed and convert to a collection of inlines.
/// </summary>
public static readonly DependencyProperty InteractiveTextProperty = DependencyProperty.RegisterAttached("InteractiveText", typeof(string), typeof(TextBlock), new PropertyMetadata(null, OnInteractiveTextChanged));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The event callback for the interactive text changed event
/// We will parse the raw text and generate the inlines that will wrap the interactive items (URL...)
/// </summary>
/// <param name="d">the object which has raised the event</param>
/// <param name="e">the change information</param>
private static void OnInteractiveTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as Windows.UI.Xaml.Controls.TextBlock;
if(textBlock == null) return;
// we remove all the inlines
textBlock.Inlines.Clear();
// if we have no data, we do not need to go further
var rawText = e.NewValue as string;
if(string.IsNullOrEmpty(rawText)) return;
var lastPosition = 0;
var matches = new Match[3];
do
{
matches[0] = UrlRegex.Match(rawText, lastPosition);
matches[1] = EmailRegex.Match(rawText, lastPosition);
matches[2] = PhoneRegex.Match(rawText, lastPosition);
var firstMatch = matches.Where(x => x.Success).OrderBy(x => x.Index).FirstOrDefault();
if(firstMatch == matches[0])
{
// the first match is an URL
CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
lastPosition = CreateUrlElement(textBlock, firstMatch);
}
else if(firstMatch == matches[1])
{
// the first match is an email
CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
lastPosition = CreateContactElement(textBlock, firstMatch, null);
}
else if(firstMatch == matches[2])
{
// the first match is a phonenumber
CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
lastPosition = CreateContactElement(textBlock, null, firstMatch);
}
else
{
// no match, we add the whole text
textBlock.Inlines.Add(new Run { Text = rawText.Substring(lastPosition) });
lastPosition = rawText.Length;
}
}
while(lastPosition < rawText.Length);
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// This method will extract a fragment of the raw text string, create a Run element with the fragment and
/// add it to the textblock inlines collection
/// </summary>
/// <param name="textBlock">the textblock where to add the run element</param>
/// <param name="rawText">the raw text where the fragment will be extracted</param>
/// <param name="startPosition">the start position to extract the fragment</param>
/// <param name="endPosition">the end position to extract the fragment</param>
private static void CreateRunElement(Windows.UI.Xaml.Controls.TextBlock textBlock, string rawText, int startPosition, int endPosition)
{
var fragment = rawText.Substring(startPosition, endPosition - startPosition);
textBlock.Inlines.Add(new Run { Text = fragment });
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// Create an URL element with the provided match result from the URL regex
/// It will create the Hyperlink element that will contain the URL and add it to the provided textblock
/// </summary>
/// <param name="textBlock">the textblock where to add the hyperlink</param>
/// <param name="urlMatch">the match for the URL to use to create the hyperlink element</param>
/// <returns>the newest position on the source string for the parsing</returns>
private static int CreateUrlElement(Windows.UI.Xaml.Controls.TextBlock textBlock, Match urlMatch)
{
Uri targetUri;
if(Uri.TryCreate(urlMatch.Value, UriKind.RelativeOrAbsolute, out targetUri))
{
var link = new Hyperlink();
link.Inlines.Add(new Run { Text= urlMatch.Value });
if(targetUri.IsAbsoluteUri)
link.NavigateUri = targetUri;
else
link.NavigateUri = new Uri(RelativeUriDefaultPrefix + targetUri.OriginalString);
textBlock.Inlines.Add(link);
}
else
{
textBlock.Inlines.Add(new Run { Text= urlMatch.Value });
}
return urlMatch.Index + urlMatch.Length;
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// Create a hyperlink element with the provided match result from the regex that will open the contact application
/// with the provided contact information (it should be a phone number or an email address
/// This is used only if the email address / phone number is not prefixed with the mailto: / tel: scheme
/// It will create the Hyperlink element that will contain the email/phone number hyperlink and add it to the provided textblock.
/// Clicking on the link will open the contact application
/// </summary>
/// <param name="textBlock">the textblock where to add the hyperlink</param>
/// <param name="emailMatch">the match for the email to use to create the hyperlink element. Set to null if not available but at least one of emailMatch and phoneMatch must be not null.</param>
/// <param name="phoneMatch">the match for the phone number to create the hyperlink element. Set to null if not available but at least one of emailMatch and phoneMatch must be not null.</param>
/// <returns>the newest position on the source string for the parsing</returns>
private static int CreateContactElement(Windows.UI.Xaml.Controls.TextBlock textBlock, Match emailMatch, Match phoneMatch)
{
var currentMatch = emailMatch ?? phoneMatch;
var link = new Hyperlink();
link.Inlines.Add(new Run { Text= currentMatch.Value });
link.Click += (s, a) =>
{
var contact = new Contact();
if(emailMatch != null) contact.Emails.Add(new ContactEmail { Address = emailMatch.Value });
if(phoneMatch != null) contact.Phones.Add(new ContactPhone { Number = phoneMatch.Value.StripNonDigitsCharacters() });
ContactManager.ShowFullContactCard(contact, new FullContactCardOptions());
};
textBlock.Inlines.Add(link);
return currentMatch.Index + currentMatch.Length;
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// Return the InteractiveText value on the provided object
/// </summary>
/// <param name="obj">the object to query</param>
/// <returns>the InteractiveText value</returns>
public static string GetInteractiveText(DependencyObject obj)
{
return (string) obj.GetValue(InteractiveTextProperty);
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// SEt the InteractiveText value on the provided object
/// </summary>
/// <param name="obj">the object to query</param>
/// <param name="value">the value to set</param>
public static void SetInteractiveText(DependencyObject obj, string value)
{
obj.SetValue(InteractiveTextProperty, value);
}

Related

MVC Custom ClientSide Validation

I have the following custom required attribute code:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class AddressRequiredAttribute : RequiredAttribute, IClientValidatable
{
/// <summary>
/// The _property name
/// </summary>
private string _propertyName;
/// <summary>
/// Initializes a new instance of the <see cref="AddressRequiredAttribute"/> class.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
public AddressRequiredAttribute(string propertyName)
: base()
{
_propertyName = propertyName;
}
/// <summary>
/// Checks that the value of the required data field is not empty.
/// </summary>
/// <param name="value">The data field value to validate.</param>
/// <returns>
/// true if validation is successful; otherwise, false.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (context.ObjectType.BaseType == typeof(AddressModel))
{
PropertyInfo property = context.ObjectType.GetProperty(_propertyName);
if (property != null && (bool)property.GetValue(context.ObjectInstance))
{
return base.IsValid(value, context);
}
}
return ValidationResult.Success;
}
/// <summary>
/// When implemented in a class, returns client validation rules for that class.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>
/// The client validation rules for this validator.
/// </returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string errorMessage = this.ErrorMessage;
// Get the specific error message if set, otherwise the default
if (string.IsNullOrEmpty(errorMessage) && metadata != null)
{
errorMessage = FormatErrorMessage(metadata.GetDisplayName());
}
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = errorMessage,
ValidationType = "requiredaddress"
};
return new[] { clientValidationRule };
}
and the following jquery for the client side validation which is run on window.load:
$.validator.addMethod('requiredaddress', function (value, element, params) {
return value != '';
}, '');
$.validator.unobtrusive.adapters.add('requiredaddress', {}, function (options) {
options.rules['requiredaddress'] = true;
options.messages['requiredaddress'] = options.message;
});
However, the clientside doesn't kick in so I get the normal clientside validation working, then the form will submit but come back with the custom errors after postback. All the examples that I have looked at say that my code should be correct so I'm not sure what is wrong here.
Can anyone see anything obvious I'm doing wrong here, thanks
I did something similar to this and found that if I put the code in the document.ready on window.load it wouldn't work. In the end I went with using the following code placed after the jquery validate scripts:
(function ($) {
$.validator.addMethod('requiredaddress', function (value, element, params) {
return value != '';
}, 'Clientside Should Not Postback');
// i think you should be able to use this as your adapter
$.validator.unobtrusive.adapters.addBool('requiredaddress');
})(jQuery);

Linq to sql table dynamically with ExecuteQuery VB

I am using VB Framework 4.0 and Linq to sql.
I want to choose dynamycally the name of table. I have used the library namedtable.dll and I have mapped all the tables of database and it's Ok.
My problem is when I try to execute executequery. Here my code.
Imports Microsoft.VisualBasic
Imports System.Data.Linq
Imports Prototype.NamedTable.Data
Imports Prototype.NamedTable.Utility
Public Class tipos
Private _conexion As String = "conexion"
Public Sub New()
End Sub
...........
Public Function getConsulta(byval tableName as String) As IList(Of TIPOS)
Dim context As New DataContext(_conexion)
sql = " select COD, NAME from " & tableName
Dim a = context.ExecuteQuery(Of TIPOS)(sql)
Return sql.ToList
End Function
End Class
but I have an error: "El tipo 'TIPOS' debe declarar un constructor predeterminado (sin parámetros) para que pueda construirse durante la asignación." that in English is:
"The type 'Type TIPOS' must declare a default (parameterless) constructor in order to be constructed during mapping"
I have defined "TIPOS" in other file:
Public Interface TIPOS
<Column(CanBeNull:=False)> Property COD Integer
<Column(CanBeNull:=False)> Property NAME As String
End Interface
Public Class ITIPO : Implements TIPO
Private _cod As Integer
Private _name As String
Public Property COD As Integer Implements TIPO.COD
Get
Return _cod
End Get
Set(ByVal value As Integer)
_cod = value
End Set
End Property
Public Property NAME As String Implements TIPO.NAME
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
I need help!
Sorry for my English.
The solution can be found on codeproject.com in article "Dynamic Table Mapping for LINQ-to-SQL." Below is a static class you can use. Please see the article for instructions on what you must do to use the 4 different generic methods. Here is an invocation example:
public interface IResult
{
[Column(IsPrimaryKey = true)]
int Id { get; set; }
[Column]
string Name { get; set; }
[Column]
double Value { get; set; }
}
public void TestThis()
{
var connectionString = "Data Source=.\SQLEXPRESS;Initial Catalog=YourDatabaseName;Integrated Security=True;Pooling=False";
var context = new DataContext(connectionString);
var table = context.GetTable<IResult>("YourTableName");
var query = from r in table where r.Id == 108 select r;
var list = query.ToList();
}
Class Code:
namespace Prototype.NamedTable
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
/// <summary>
/// The utility.
/// </summary>
public static class Utility
{
#region Constants and Fields
/// <summary>
/// The named types.
/// </summary>
private static readonly Dictionary<string, Type> NamedTypes = new Dictionary<string, Type>();
/// <summary>
/// The _assembly builder.
/// </summary>
private static AssemblyBuilder _assemblyBuilder;
/// <summary>
/// The _module builder.
/// </summary>
private static ModuleBuilder _moduleBuilder;
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether Verbose.
/// </summary>
public static bool Verbose { get; set; }
#endregion
#region Public Methods
/// <summary>
/// The clear.
/// </summary>
public static void Clear()
{
_assemblyBuilder = null;
NamedTypes.Clear();
}
/// <summary>
/// Retrieve a table from the data context which implements ITable<TEntity> by T and use ITable<TBack>
/// </summary>
/// <typeparam name="TEntity">
/// Entity Type
/// </typeparam>
/// <typeparam name="TBack">
/// Backing Type
/// </typeparam>
/// <param name="context">
/// Data Context
/// </param>
/// <returns>
/// </returns>
public static ATable<TEntity> GetTable<TEntity, TBack>(this DataContext context) where TEntity : class
where TBack : class
{
// Create the backup table
Table<TBack> refer = context.GetTable<TBack>();
// Prepare the cloning method
Delegate cloneFrom = CompileCloning(typeof(TEntity), typeof(TBack));
// Construct the table wrapper
return new ATable<TEntity>(refer, cloneFrom);
}
/// <summary>
/// Retrieve a table from the data context which implements ITable<TEntity> uses specific backing table
/// </summary>
/// <typeparam name="TEntity">
/// Entity Type
/// </typeparam>
/// <param name="context">
/// Data context
/// </param>
/// <param name="name">
/// Table name
/// </param>
/// <returns>
/// </returns>
public static ATable<TEntity> GetTable<TEntity>(this DataContext context, string name) where TEntity : class
{
// Create/Retrieve a type definition for the table using the TEntity type
Type type = DefineEntityType(typeof(TEntity), name);
// Create the backup table using the new type
ITable refer = context.GetTable(type);
// Prepare the cloning method
Delegate cloneFrom = CompileCloning(typeof(TEntity), type);
// Construct the table wrapper
return new ATable<TEntity>(refer, cloneFrom);
}
/*
/// <summary>
/// The log.
/// </summary>
/// <param name="format">
/// The format.
/// </param>
/// <param name="args">
/// The args.
/// </param>
public static void Log(string format, params object[] args)
{
if (!Verbose)
{
return;
}
Console.Write("*** ");
if ((args == null) || (args.Length == 0))
{
Console.WriteLine(format);
}
else
{
Console.WriteLine(format, args);
}
}*/
#endregion
#region Methods
/// <summary>
/// Clone an attribute
/// </summary>
/// <param name="attr">
/// </param>
/// <returns>
/// </returns>
private static CustomAttributeBuilder CloneColumn(object attr)
{
Type source = attr.GetType();
Type target = typeof(ColumnAttribute);
var props = new List<PropertyInfo>();
var values = new List<object>();
// Extract properties and their values
foreach (PropertyInfo prop in source.GetProperties())
{
if (!prop.CanRead || !prop.CanWrite)
{
continue;
}
props.Add(target.GetProperty(prop.Name));
values.Add(prop.GetValue(attr, null));
}
// Create a new attribute using the properties and values
return new CustomAttributeBuilder(
target.GetConstructor(Type.EmptyTypes), new object[0], props.ToArray(), values.ToArray());
}
/// <summary>
/// Make a delegate that copy content from "source" to "dest"
/// </summary>
/// <param name="source">
/// Source Type
/// </param>
/// <param name="dest">
/// Destination Type
/// </param>
/// <returns>
/// Executable delegate
/// </returns>
private static Delegate CompileCloning(Type source, Type dest)
{
// Input parameter
ParameterExpression input = Expression.Parameter(source);
// For every property, create a member binding
List<MemberBinding> binds =
source.GetProperties().Select(
prop =>
Expression.Bind(
dest.GetProperty(
prop.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly),
Expression.MakeMemberAccess(input, prop))).Cast<MemberBinding>().ToList();
// Expression of creating the new object
MemberInitExpression body = Expression.MemberInit(
Expression.New(dest.GetConstructor(Type.EmptyTypes)), binds);
// The final lambda
LambdaExpression lambda = Expression.Lambda(body, input);
// MJE
//Log("{0}", lambda.ToString());
// Return the executable delegate
return lambda.Compile();
}
/// <summary>
/// Create a class based on the template interface
/// </summary>
/// <param name="template">
/// </param>
/// <param name="name">
/// </param>
/// <returns>
/// </returns>
private static Type DefineEntityType(Type template, string name)
{
// Prepare the builders if not done
if (_assemblyBuilder == null)
{
_assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run);
_moduleBuilder = _assemblyBuilder.DefineDynamicModule("Types");
}
// Check if there is already a type created for that table
if (NamedTypes.ContainsKey(name))
{
return NamedTypes[name];
}
// Create the new type
TypeBuilder tbuilder = null;
if (template.IsInterface)
{
tbuilder = DefineInterfaceChild(name, template);
}
else
{
tbuilder = DefineOverriddenChild(name, template);
}
Type final = tbuilder.CreateType();
NamedTypes[name] = final;
return final;
}
/// <summary>
/// The define interface child.
/// </summary>
/// <param name="name">
/// The name.
/// </param>
/// <param name="template">
/// The template.
/// </param>
/// <returns>
/// </returns>
private static TypeBuilder DefineInterfaceChild(string name, Type template)
{
TypeBuilder tbuilder = _moduleBuilder.DefineType(
name, TypeAttributes.Public, typeof(Object), new[] { template });
// Default constructor
tbuilder.DefineDefaultConstructor(MethodAttributes.Public);
// Attach Table attribute
var abuilder = new CustomAttributeBuilder(
typeof(TableAttribute).GetConstructor(Type.EmptyTypes),
new object[0],
new[] { typeof(TableAttribute).GetProperty("Name") },
new object[] { name });
tbuilder.SetCustomAttribute(abuilder);
List<PropertyInfo> properties = template.GetProperties().ToList(); // May require sorting
// Implement all properties));
foreach (PropertyInfo prop in properties)
{
// Define backing field
FieldBuilder fbuilder = tbuilder.DefineField(
"_" + prop.Name, prop.PropertyType, FieldAttributes.Private);
// Define get method
MethodBuilder pgbuilder = tbuilder.DefineMethod(
"get_" + prop.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig
| MethodAttributes.Virtual | MethodAttributes.Final,
prop.PropertyType,
Type.EmptyTypes);
// Define get method body { return _field; }
ILGenerator ilg = pgbuilder.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Ldfld, fbuilder);
ilg.Emit(OpCodes.Ret);
// Define set method
MethodBuilder psbuilder = tbuilder.DefineMethod(
"set_" + prop.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig
| MethodAttributes.Virtual | MethodAttributes.Final,
null,
new[] { prop.PropertyType });
// Define set method body { _field = value; }
ILGenerator ils = psbuilder.GetILGenerator();
ils.Emit(OpCodes.Ldarg_0);
ils.Emit(OpCodes.Ldarg_1);
ils.Emit(OpCodes.Stfld, fbuilder);
ils.Emit(OpCodes.Ret);
// Define the property
PropertyBuilder pbuilder = tbuilder.DefineProperty(
prop.Name, PropertyAttributes.None, CallingConventions.Standard, prop.PropertyType, null);
// Set get/set method
pbuilder.SetGetMethod(pgbuilder);
pbuilder.SetSetMethod(psbuilder);
// Attach Column attribute
foreach (object attr in prop.GetCustomAttributes(false))
{
if (attr is ColumnAttribute || attr is AlterColumnAttribute)
{
// MJE
//Log("Create column attribute for {0}", prop.Name);
pbuilder.SetCustomAttribute(CloneColumn(attr));
break;
}
}
}
return tbuilder;
}
/// <summary>
/// The define overridden child.
/// </summary>
/// <param name="name">
/// The name.
/// </param>
/// <param name="template">
/// The template.
/// </param>
/// <returns>
/// </returns>
private static TypeBuilder DefineOverriddenChild(string name, Type template)
{
TypeBuilder tbuilder = _moduleBuilder.DefineType(name, TypeAttributes.Public, template);
// Default constructor
tbuilder.DefineDefaultConstructor(MethodAttributes.Public);
// Attach Table attribute
var abuilder = new CustomAttributeBuilder(
typeof(TableAttribute).GetConstructor(Type.EmptyTypes),
new object[0],
new[] { typeof(TableAttribute).GetProperty("Name") },
new object[] { name });
tbuilder.SetCustomAttribute(abuilder);
List<PropertyInfo> properties = template.GetProperties().ToList(); // May require sorting
// Implement all properties));
foreach (PropertyInfo prop in properties)
{
// Define get method
MethodBuilder pgbuilder = tbuilder.DefineMethod(
"get_" + prop.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig
| MethodAttributes.Virtual | MethodAttributes.Final,
prop.PropertyType,
Type.EmptyTypes);
// Define get method body { return _field; }
ILGenerator ilg = pgbuilder.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Call, template.GetMethod("get_" + prop.Name));
ilg.Emit(OpCodes.Ret);
// Define set method
MethodBuilder psbuilder = tbuilder.DefineMethod(
"set_" + prop.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig
| MethodAttributes.Virtual | MethodAttributes.Final,
null,
new[] { prop.PropertyType });
// Define set method body { _field = value; }
ILGenerator ils = psbuilder.GetILGenerator();
ils.Emit(OpCodes.Ldarg_0);
ils.Emit(OpCodes.Ldarg_1);
ils.Emit(OpCodes.Call, template.GetMethod("set_" + prop.Name));
ils.Emit(OpCodes.Ret);
// Define the property
PropertyBuilder pbuilder = tbuilder.DefineProperty(
prop.Name, PropertyAttributes.None, CallingConventions.Standard, prop.PropertyType, null);
// Set get/set method
pbuilder.SetGetMethod(pgbuilder);
pbuilder.SetSetMethod(psbuilder);
// Attach Column attribute
foreach (object attr in prop.GetCustomAttributes(false))
{
if (attr is ColumnAttribute || attr is AlterColumnAttribute)
{
// MJE
//Log("Create column attribute for {0}", prop.Name);
pbuilder.SetCustomAttribute(CloneColumn(attr));
break;
}
}
}
return tbuilder;
}
#endregion
/// <summary>
/// A table wrapper implements ITable<TEntity> backed by other ITable object
/// </summary>
/// <typeparam name="TEntity">
/// </typeparam>
public class ATable<TEntity> : ITable<TEntity>
where TEntity : class
{
#region Constants and Fields
/// <summary>
/// Cloning method
/// </summary>
private readonly Delegate _clone;
/// <summary>
/// Backing table
/// </summary>
private readonly ITable _internal;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="ATable{TEntity}"/> class.
/// Construct from backing table
/// </summary>
/// <param name="inter">
/// </param>
/// <param name="from">
/// </param>
public ATable(ITable inter, Delegate from)
{
this._internal = inter;
this._clone = from;
}
#endregion
#region Properties
/// <summary>
/// Gets ElementType.
/// </summary>
public Type ElementType
{
get
{
// Use the backing table element
return this._internal.ElementType;
}
}
/// <summary>
/// Gets Expression.
/// </summary>
public Expression Expression
{
get
{
// Use the backing table expression
return this._internal.Expression;
}
}
/// <summary>
/// Gets Provider.
/// </summary>
public IQueryProvider Provider
{
get
{
// Use the backing table provider
return this._internal.Provider;
}
}
#endregion
#region Implemented Interfaces
#region IEnumerable
/// <summary>
/// The get enumerator.
/// </summary>
/// <returns>
/// </returns>
/// <exception cref="NotImplementedException">
/// </exception>
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
#endregion
#region IEnumerable<TEntity>
/// <summary>
/// The get enumerator.
/// </summary>
/// <returns>
/// </returns>
/// <exception cref="NotImplementedException">
/// </exception>
public IEnumerator<TEntity> GetEnumerator()
{
throw new NotImplementedException();
}
#endregion
#region ITable<TEntity>
/// <summary>
/// The attach.
/// </summary>
/// <param name="entity">
/// The entity.
/// </param>
/// <exception cref="NotImplementedException">
/// </exception>
public void Attach(TEntity entity)
{
throw new NotImplementedException();
}
/// <summary>
/// The delete on submit.
/// </summary>
/// <param name="entity">
/// The entity.
/// </param>
public void DeleteOnSubmit(TEntity entity)
{
// Directly invoke the backing table
this._internal.DeleteOnSubmit(entity);
}
/// <summary>
/// The insert on submit.
/// </summary>
/// <param name="entity">
/// The entity.
/// </param>
public void InsertOnSubmit(TEntity entity)
{
// Input entity must be changed to backing type
object v = this._clone.DynamicInvoke(entity);
// Invoke the backing table
this._internal.InsertOnSubmit(v);
}
#endregion
#endregion
}
/// <summary>
/// The alter column attribute.
/// </summary>
public class AlterColumnAttribute : Attribute
{
#region Constants and Fields
/// <summary>
/// The _can be null.
/// </summary>
private bool _canBeNull = true;
/// <summary>
/// The _update check.
/// </summary>
private UpdateCheck _updateCheck = UpdateCheck.Always;
#endregion
#region Properties
/// <summary>
/// Gets or sets AutoSync.
/// </summary>
public AutoSync AutoSync { get; set; }
/// <summary>
/// Gets or sets a value indicating whether CanBeNull.
/// </summary>
public bool CanBeNull
{
get
{
return this._canBeNull;
}
set
{
this._canBeNull = value;
}
}
/// <summary>
/// Gets or sets DbType.
/// </summary>
public string DbType { get; set; }
/// <summary>
/// Gets or sets Expression.
/// </summary>
public string Expression { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDbGenerated.
/// </summary>
public bool IsDbGenerated { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDiscriminator.
/// </summary>
public bool IsDiscriminator { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsPrimaryKey.
/// </summary>
public bool IsPrimaryKey { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsVersion.
/// </summary>
public bool IsVersion { get; set; }
/// <summary>
/// Gets or sets UpdateCheck.
/// </summary>
public UpdateCheck UpdateCheck
{
get
{
return this._updateCheck;
}
set
{
this._updateCheck = value;
}
}
#endregion
}
}
}
Linq-to-Sql cannot materialize interfaces. It needs a class specification to know what instances it should create from a query. The exception message is elusive, to say the least. I don't know why it isn't more to the point.
Note that the class you want to materialize must have been mapped, or: it must be in the dbml. I say this because your ITIPO class is not partial, which makes me wonder how you can make it implement an interface (well, maybe you just slimmed down the code).
Side note: don't use all capitals for class names, and prefix an interface specification with "I", not a class.

WCF multipart/form data with multiple files

I can't find any examples anywhere on how to process multipart/form data which contains multiple files in the request.
I'm trying to build a WCF service endpoint which contains a set of parameters in a text file, and then two image files, all in all including three files in one post. Using Fiddler, I am able to build the request and it looks like this:
HEADERS: Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
User-Agent: Fiddler
Host: dhiibews.brandonb.com
Content-Length: 107865
REQUESTBODY: ---------------------------acebdf13572468
Content-Disposition: form-data; name="Json"; filename="depositcheckrequest.txt"
Content-Type: application/json
<#INCLUDE *C:\depositcheckrequest.txt*#>
---------------------------acebdf13572468
Content-Disposition: form-data; name="frontImage"; filename="front.jpg"
Content-Type: image/jpeg
<#INCLUDE *C:\front.jpg*#>
---------------------------acebdf13572468
Content-Disposition: form-data; name="rearImage"; filename="rear.jpg"
Content-Type: image/jpeg
<#INCLUDE *C:\rear.jpg*#>
---------------------------acebdf13572468--
The include tags basically end up spitting out the file content as raw data.
I've been searching and all I can find are people who can tell me how to get the information for one file which in my case ends up being the first one. I don't want to get just one file though; as you can see I am trying to upload THREE files.
How do I parse multiple files in a single multipart/form data POST request?
Ok I figured out a solution. It turns out that the framework Nancyfx includes a multipart data processor which separates the boundaries for you into substreams. So I stole the relevant files from Nancy's source code and copied them into my project. The relevant files are:
HttpMultipart.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MyProjectNamespace
{
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
/// <summary>
/// Retrieves <see cref="HttpMultipartBoundary"/> instances from a request stream.
/// </summary>
public class HttpMultipart
{
private const byte LF = (byte)'\n';
private readonly byte[] boundaryAsBytes;
private readonly HttpMultipartBuffer readBuffer;
private readonly MemoryStream requestStream;
private readonly byte[] closingBoundaryAsBytes;
/// <summary>
/// Initializes a new instance of the <see cref="HttpMultipart"/> class.
/// </summary>
/// <param name="requestStream">The request stream to parse.</param>
/// <param name="boundary">The boundary marker to look for.</param>
public HttpMultipart(Stream requestStream, string boundary)
{
this.requestStream = new MemoryStream(ToByteArray(requestStream));
this.boundaryAsBytes = GetBoundaryAsBytes(boundary, false);
this.closingBoundaryAsBytes = GetBoundaryAsBytes(boundary, true);
this.readBuffer = new HttpMultipartBuffer(this.boundaryAsBytes, this.closingBoundaryAsBytes);
}
/// <summary>
/// Gets the <see cref="HttpMultipartBoundary"/> instances from the request stream.
/// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> instance, containing the found <see cref="HttpMultipartBoundary"/> instances.</returns>
public IEnumerable<HttpMultipartBoundary> GetBoundaries()
{
return
(from boundaryStream in this.GetBoundarySubStreams()
select new HttpMultipartBoundary(boundaryStream)).ToList();
}
private static byte[] GetBoundaryAsBytes(string boundary, bool closing)
{
var boundaryBuilder = new StringBuilder();
boundaryBuilder.Append("--");
boundaryBuilder.Append(boundary);
if (closing)
{
boundaryBuilder.Append("--");
}
else
{
boundaryBuilder.Append('\r');
boundaryBuilder.Append('\n');
}
var bytes =
Encoding.ASCII.GetBytes(boundaryBuilder.ToString());
return bytes;
}
private IEnumerable<HttpMultipartSubStream> GetBoundarySubStreams()
{
var boundarySubStreams = new List<HttpMultipartSubStream>();
var boundaryStart = this.GetNextBoundaryPosition();
while (MultipartIsNotCompleted(boundaryStart))
{
var boundaryEnd = this.GetNextBoundaryPosition();
boundarySubStreams.Add(new HttpMultipartSubStream(
this.requestStream,
boundaryStart,
this.GetActualEndOfBoundary(boundaryEnd)));
boundaryStart = boundaryEnd;
}
return boundarySubStreams;
}
private bool MultipartIsNotCompleted(long boundaryPosition)
{
return boundaryPosition > -1 && !this.readBuffer.IsClosingBoundary;
}
//we add two because or the \r\n before the boundary
private long GetActualEndOfBoundary(long boundaryEnd)
{
if (this.CheckIfFoundEndOfStream())
{
return this.requestStream.Position - (this.readBuffer.Length + 2);
}
return boundaryEnd - (this.readBuffer.Length + 2);
}
private bool CheckIfFoundEndOfStream()
{
return this.requestStream.Position.Equals(this.requestStream.Length);
}
private long GetNextBoundaryPosition()
{
this.readBuffer.Reset();
while (true)
{
var byteReadFromStream = this.requestStream.ReadByte();
if (byteReadFromStream == -1)
{
return -1;
}
this.readBuffer.Insert((byte)byteReadFromStream);
if (this.readBuffer.IsFull && (this.readBuffer.IsBoundary || this.readBuffer.IsClosingBoundary))
{
return this.requestStream.Position;
}
if (byteReadFromStream.Equals(LF) || this.readBuffer.IsFull)
{
this.readBuffer.Reset();
}
}
}
private byte[] ToByteArray(Stream stream)
{
byte[] buffer = new byte[32768];
using (MemoryStream ms = new MemoryStream())
{
while (true)
{
int read = stream.Read(buffer, 0, buffer.Length);
if (read <= 0)
{
return ms.ToArray();
}
ms.Write(buffer, 0, read);
}
}
}
}
}
HttpMultipartBuffer.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MyProjectNamespace
{
public class HttpMultipartBuffer
{
private readonly byte[] boundaryAsBytes;
private readonly byte[] closingBoundaryAsBytes;
private readonly byte[] buffer;
private int position;
/// <summary>
/// Initializes a new instance of the <see cref="HttpMultipartBuffer"/> class.
/// </summary>
/// <param name="boundaryAsBytes">The boundary as a byte-array.</param>
/// <param name="closingBoundaryAsBytes">The closing boundary as byte-array</param>
public HttpMultipartBuffer(byte[] boundaryAsBytes, byte[] closingBoundaryAsBytes)
{
this.boundaryAsBytes = boundaryAsBytes;
this.closingBoundaryAsBytes = closingBoundaryAsBytes;
this.buffer = new byte[this.boundaryAsBytes.Length];
}
/// <summary>
/// Gets a value indicating whether the buffer contains the same values as the boundary.
/// </summary>
/// <value><see langword="true"/> if buffer contains the same values as the boundary; otherwise, <see langword="false"/>.</value>
public bool IsBoundary
{
get { return this.buffer.SequenceEqual(this.boundaryAsBytes); }
}
public bool IsClosingBoundary
{
get { return this.buffer.SequenceEqual(this.closingBoundaryAsBytes); }
}
/// <summary>
/// Gets a value indicating whether this buffer is full.
/// </summary>
/// <value><see langword="true"/> if buffer is full; otherwise, <see langword="false"/>.</value>
public bool IsFull
{
get { return this.position.Equals(this.buffer.Length); }
}
/// <summary>
/// Gets the the number of bytes that can be stored in the buffer.
/// </summary>
/// <value>The number of butes that can be stored in the buffer.</value>
public int Length
{
get { return this.buffer.Length; }
}
/// <summary>
/// Resets the buffer so that inserts happens from the start again.
/// </summary>
/// <remarks>This does not clear any previously written data, just resets the buffer position to the start. Data that is inserted after Reset has been called will overwrite old data.</remarks>
public void Reset()
{
this.position = 0;
}
/// <summary>
/// Inserts the specified value into the buffer and advances the internal position.
/// </summary>
/// <param name="value">The value to insert into the buffer.</param>
/// <remarks>This will throw an <see cref="ArgumentOutOfRangeException"/> is you attempt to call insert more times then the <see cref="Length"/> of the buffer and <see cref="Reset"/> was not invoked.</remarks>
public void Insert(byte value)
{
this.buffer[this.position++] = value;
}
}
}
HttpMultipartBoundary.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace DHIWebSvc.Models
{
public class HttpMultipartBoundary
{
private const byte LF = (byte)'\n';
private const byte CR = (byte)'\r';
/// <summary>
/// Initializes a new instance of the <see cref="HttpMultipartBoundary"/> class.
/// </summary>
/// <param name="boundaryStream">The stream that contains the boundary information.</param>
public HttpMultipartBoundary(HttpMultipartSubStream boundaryStream)
{
this.Value = boundaryStream;
this.ExtractHeaders();
}
/// <summary>
/// Gets the contents type of the boundary value.
/// </summary>
/// <value>A <see cref="string"/> containing the name of the value if it is available; otherwise <see cref="string.Empty"/>.</value>
public string ContentType { get; private set; }
/// <summary>
/// Gets or the filename for the boundary value.
/// </summary>
/// <value>A <see cref="string"/> containing the filename value if it is available; otherwise <see cref="string.Empty"/>.</value>
/// <remarks>This is the RFC2047 decoded value of the filename attribute of the Content-Disposition header.</remarks>
public string Filename { get; private set; }
/// <summary>
/// Gets name of the boundary value.
/// </summary>
/// <remarks>This is the RFC2047 decoded value of the name attribute of the Content-Disposition header.</remarks>
public string Name { get; private set; }
/// <summary>
/// A stream containig the value of the boundary.
/// </summary>
/// <remarks>This is the RFC2047 decoded value of the Content-Type header.</remarks>
public HttpMultipartSubStream Value { get; private set; }
private void ExtractHeaders()
{
while (true)
{
var header =
this.ReadLineFromStream();
if (string.IsNullOrEmpty(header))
{
break;
}
if (header.StartsWith("Content-Disposition", StringComparison.CurrentCultureIgnoreCase))
{
this.Name = Regex.Match(header, #"name=""(?<name>[^\""]*)", RegexOptions.IgnoreCase).Groups["name"].Value;
this.Filename = Regex.Match(header, #"filename=""(?<filename>[^\""]*)", RegexOptions.IgnoreCase).Groups["filename"].Value;
}
if (header.StartsWith("Content-Type", StringComparison.InvariantCultureIgnoreCase))
{
this.ContentType = header.Split(new[] { ' ' }).Last().Trim();
}
}
this.Value.PositionStartAtCurrentLocation();
}
private string ReadLineFromStream()
{
var readBuffer = new StringBuilder();
while (true)
{
var byteReadFromStream = this.Value.ReadByte();
if (byteReadFromStream == -1)
{
return null;
}
if (byteReadFromStream.Equals(LF))
{
break;
}
readBuffer.Append((char)byteReadFromStream);
}
var lineReadFromStream =
readBuffer.ToString().Trim(new[] { (char)CR });
return lineReadFromStream;
}
}
}
and finally...
HttpMultipartSubStream.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace DHIWebSvc.Models
{
public class HttpMultipartSubStream : Stream
{
private readonly Stream stream;
private readonly long end;
private long start;
private long position;
/// <summary>
/// Initializes a new instance of the <see cref="HttpMultipartSubStream"/> class.
/// </summary>
/// <param name="stream">The stream to create the sub-stream ontop of.</param>
/// <param name="start">The start offset on the parent stream where the sub-stream should begin.</param>
/// <param name="end">The end offset on the parent stream where the sub-stream should end.</param>
public HttpMultipartSubStream(Stream stream, long start, long end)
{
this.stream = stream;
this.start = start;
this.position = start;
this.end = end;
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
/// <returns><see langword="true"/> if the stream supports reading; otherwise, <see langword="false"/>.</returns>
public override bool CanRead
{
get { return true; }
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
/// <returns><see langword="true"/> if the stream supports seeking; otherwise, <see langword="false"/>.</returns>
public override bool CanSeek
{
get { return true; }
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
/// <returns><see langword="true"/> if the stream supports writing; otherwise, <see langword="false"/>.</returns>
public override bool CanWrite
{
get { return false; }
}
/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
/// <returns>A long value representing the length of the stream in bytes.</returns>
/// <exception cref="NotSupportedException">A class derived from Stream does not support seeking. </exception><exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
public override long Length
{
get
{
return this.end - this.start;
}
}
/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
/// <returns>
/// The current position within the stream.
/// </returns>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception><exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception><exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception><filterpriority>1</filterpriority>
public override long Position
{
get { return this.position - this.start; }
set { this.position = this.Seek(value, SeekOrigin.Begin); }
}
public void PositionStartAtCurrentLocation()
{
this.start = this.stream.Position;
}
/// <summary>
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
/// <remarks>In the <see cref="HttpMultipartSubStream"/> type this method is implemented as no-op.</remarks>
public override void Flush()
{
}
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. </returns>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset"/> and (<paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the current source. </param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream. </param>
public override int Read(byte[] buffer, int offset, int count)
{
if (count > (this.end - this.position))
{
count = (int)(this.end - this.position);
}
if (count <= 0)
{
return 0;
}
this.stream.Position = this.position;
var bytesReadFromStream =
this.stream.Read(buffer, offset, count);
this.RepositionAfterRead(bytesReadFromStream);
return bytesReadFromStream;
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
/// </summary>
/// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
public override int ReadByte()
{
if (this.position >= this.end)
{
return -1;
}
this.stream.Position = this.position;
var byteReadFromStream = this.stream.ReadByte();
this.RepositionAfterRead(1);
return byteReadFromStream;
}
/// <summary>
/// When overridden in a derived class, sets the position within the current stream.
/// </summary>
/// <returns>The new position within the current stream.</returns>
/// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
/// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
public override long Seek(long offset, SeekOrigin origin)
{
var subStreamRelativePosition =
this.CalculateSubStreamRelativePosition(origin, offset);
this.ThrowExceptionIsPositionIsOutOfBounds(subStreamRelativePosition);
this.position = this.stream.Seek(subStreamRelativePosition, SeekOrigin.Begin);
return this.position;
}
/// <summary>
/// When overridden in a derived class, sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
/// <remarks>This will always throw a <see cref="InvalidOperationException"/> for the <see cref="HttpMultipartSubStream"/> type.</remarks>
public override void SetLength(long value)
{
throw new InvalidOperationException();
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream. </param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream. </param>
/// <param name="count">The number of bytes to be written to the current stream. </param>
/// <remarks>This will always throw a <see cref="InvalidOperationException"/> for the <see cref="HttpMultipartSubStream"/> type.</remarks>
public override void Write(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException();
}
private void ThrowExceptionIsPositionIsOutOfBounds(long subStreamRelativePosition)
{
if (subStreamRelativePosition < 0 || subStreamRelativePosition > this.end)
{
throw new InvalidOperationException();
}
}
private long CalculateSubStreamRelativePosition(SeekOrigin origin, long offset)
{
var subStreamRelativePosition = 0L;
switch (origin)
{
case SeekOrigin.Begin:
subStreamRelativePosition = this.start + offset;
break;
case SeekOrigin.Current:
subStreamRelativePosition = this.position + offset;
break;
case SeekOrigin.End:
subStreamRelativePosition = this.end + offset;
break;
}
return subStreamRelativePosition;
}
private void RepositionAfterRead(int bytesReadFromStream)
{
if (bytesReadFromStream == -1)
{
this.position = this.end;
}
else
{
this.position += bytesReadFromStream;
}
}
}
}
The usage is relatively simple for what I was trying to do. I simply defined a boundary and only look for specific part names. So for the example in my question, I used the boundary "-------------------------acebdf13572468" with three files: Json, frontImage, and rearImage.

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;
}
}
}
}

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

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.