I am using the MSBuild copy and then Concat to concatenate multiple SQL files. Some of the SQL files are in some other format (opens in notepad), but after Concat gets to the files, there are strange characters all over the resulting files.
Can anyone help me, has anyone experienced this before? How can I convert all SQL files to ascii, or make the Concat task work properly?
Well I got around it by writing a utility C# class. It is not designed to handle exceptions, they are handled by the calling class or unhandled in the console.
It converts the files to ASCII, removes any invalid characters and rewrites the files.
I spent a while wondering why I was getting access-denied issues, make sure you have all the files you want to convert checked out before you convert them!
Here it is if you are interested:
using System;
using System.IO;
using System.Text;
/// <summary>
/// Manages encoding of text files.
/// </summary>
public class TextFileEncoding
{
/// <summary>
/// Main command-line entry.
/// </summary>
/// <param name="args"></param>
public static void Main(string[] args)
{
if (args.Length > 0)
{
string argParam = args[0].ToString().Trim().ToLower();
switch (argParam)
{
// Return encoding for a file.
case "get":
Encoding enc = GetEncoding(args[1].ToString());
Console.WriteLine(enc.EncodingName);
break;
// convert encoding for a path with a given pattern to ASCII.
case "toascii":
int count = ConvertToAscii(args[1].ToString(), args.Length == 3 ? args[2] : "*.*");
Console.WriteLine("Successfully converted " + count + " files.");
break;
default:
Console.WriteLine("Invalid parameter. Expected get or toascii");
break;
}
}
else
{
Console.WriteLine("Missing filename parameter.\nFormat: TextFileEncoding.exe [get|toascii] <TextFile> <Path> <Pattern>");
}
}
/// <summary>
/// Returns the encoding of the filename at the specified path.
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public static Encoding GetEncoding(string filename)
{
StreamReader fileStream = new StreamReader(filename, true);
return GetEncoding(fileStream);
}
/// <summary>
/// Returns the encoding of the file stream.
/// </summary>
/// <param name="fileStream"></param>
/// <returns></returns>
public static Encoding GetEncoding(StreamReader fileStream)
{
return fileStream.CurrentEncoding;
}
/// <summary>
/// Converts all files to ascii encoding.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static int ConvertToAscii(string path, string pattern)
{
DirectoryInfo pathInfo = new DirectoryInfo(path);
int count = 0;
// Get files in directory, recursively.
FileInfo[] files = pathInfo.GetFiles(pattern, SearchOption.AllDirectories);
foreach (FileInfo file in files)
{
// Encode to ASCII.
if (EncodeAscii(file))
{
count++;
}
}
return count;
}
/// <summary>
/// Converts file to ascii.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
private static bool EncodeAscii(FileInfo file)
{
ASCIIEncoding encoder = new ASCIIEncoding();
// Get content of file as string.
string content = File.ReadAllText(file.FullName);
// Convert to an ASCII byte array.
byte[] asciiString = encoder.GetBytes(content);
// Convert to string, so all unknown characters get removed.
string cleanString = encoder.GetString(asciiString);
// Convert back to byte array for writing to file.
byte[] cleanBytes = encoder.GetBytes(cleanString);
// Delete and rewrite file as ASCII.
file.Delete();
File.WriteAllBytes(file.FullName, cleanBytes);
return true;
}
}
Related
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);
}
I am attempting to upload a file through a Silverlight client using the following MessageContract:
[MessageContract]
public class CategoryClientFileTransferMC : IDisposable
{
/// <summary>
/// CategoryID - Category identity.
/// </summary>
[MessageHeader(MustUnderstand = true)]
public int CategoryID;
/// <summary>
/// ID - File identifier.
/// </summary>
[MessageHeader(MustUnderstand = true)]
public string ID;
/// <summary>
/// Length - File length in bytes.
/// </summary>
[MessageHeader(MustUnderstand = true)]
public long Length;
/// <summary>
/// FileByteStream - File stream.
/// </summary>
[MessageBodyMember(Order = 1)]
public Stream FileByteStream;
/// <summary>
/// Dispose the contract.
/// </summary>
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
My problem is that the generated operation method on the client only takes a single argument; a byte array called FileByteStream. In other (non-Silverlight) clients I've created it asks for the MemberHeader fields as well. Without specifying these headers, the server has no idea what to do with the file. How can I set these headers when I call the operation?
Also, is there a better way to upload a file from a Silverlight client? This has been a huge headache.
Thanks.
The Silverlight subset of the WCF client does not support the [MessageHeader] attribute. You can still set message headers, but it's not as straightforward as in other platforms. Basically, you'll need to set the headers using the operation context, prior to making the call, like in the example below:
var client = new SilverlightReference1.MyClient();
using (new OperationContextScope(client.InnerChannel))
{
string contractNamespace = "http://tempuri.org/";
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("CategoryId", contractNamespace, 1));
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("ID", contractNamespace, "abc123"));
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("Length", contractNamespace, 123456L));
client.UploadFile(myFileContents);
}
Where contractNamespace is the XML namespace for the message header fields (IIRC they default to the same as the service contract). You can use Fiddler and something like the WCF Test Client to see which namespace is used there.
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.
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;
}
}
}
}
I'm currently working on an application which writes data to the IsolatedStorageStore. As part of the app, I'd like to implement a "clear all data/reset" button, but enumerating through all the files that exist and all the folders that exist is taking quite a bit of time. Is there a magic "reset" method or something I can use, or should I instead focus on optimizing the manual deletion process?
Or can I get away with not providing such functionality, and leave it to the user to uninstall/reinstall the application for a reset?
My hideous delete-all-files method is below:
/// <summary>
/// deletes all files in specified folder
/// </summary>
/// <param name="sPath"></param>
public static void ClearFolder(String sPath, IsolatedStorageFile appStorage)
{
//delete all files
string[] filenames = GetFilenames(sPath);
if (filenames != null)
{
foreach (string sFile in filenames)
{
DeleteFile(System.IO.Path.Combine(sPath, sFile));
}
}
//delete all subfolders if directory still exists
try
{
foreach (string sDirectory in appStorage.GetDirectoryNames(sPath))
{
ClearFolder(System.IO.Path.Combine(sPath, sDirectory) + #"\", appStorage);
}
}
catch (DirectoryNotFoundException ex)
{
//current clearing folder was deleted / no longer exists - return
return;
}
//try to delete this folder
try
{
appStorage.DeleteDirectory(sPath);
}
catch (ArgumentException ex) { }
}
/// <summary>
/// Attempts to delete a file from isolated storage - if the directory will be empty, it is also removed.
/// </summary>
/// <param name="sPath"></param>
/// <returns></returns>
public static void DeleteFile(string sPath)
{
using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
appStorage.DeleteFile(sPath);
String sDirectory = System.IO.Path.GetDirectoryName(sPath);
//if this was the last file inside this folder, remove the containing folder
if (appStorage.GetFileNames(sPath).Length == 0)
{
appStorage.DeleteDirectory(sDirectory);
}
}
}
/// <summary>
/// Returns an array of filenames in a given directory
/// </summary>
/// <param name="sHistoryFolder"></param>
/// <returns></returns>
public static string[] GetFilenames(string sDirectory)
{
using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
try
{
return appStorage.GetFileNames(sDirectory);
}
catch (DirectoryNotFoundException)
{
return null;
}
}
}
You're looking for the Remove() method.
Use it like this:
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.Remove();
}