Does anybody have any idea about how to output minified HTML and JavaScript from the Razor engine while keeping custom coding styles?
For example: i want the following code:
<div
#if (Model.Name != string.Empty)
#:id="#Model.Name"
>
</div>
To be outputted as <div id="DivId"></div> .
Look at http://arranmaclean.wordpress.com/2010/08/10/minify-html-with-net-mvc-actionfilter/.
there is an example for creating custom action filter witch clear html from WhiteSpaces
Update: The source code quoted from above.
The stream class for removing "blanks"
using System;
using System.IO;
using System.Text;
using System.Web.Mvc;
using System.Text.RegularExpressions;
namespace RemoveWhiteSpace.ActionFilters
{
public class WhiteSpaceFilter : Stream
{
private Stream _shrink;
private Func<string, string> _filter;
public WhiteSpaceFilter(Stream shrink, Func<string, string> filter)
{
_shrink = shrink;
_filter = filter;
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return true; } }
public override void Flush() { _shrink.Flush(); }
public override long Length { get { return 0; } }
public override long Position { get; set; }
public override int Read(byte[] buffer, int offset, int count)
{
return _shrink.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _shrink.Seek(offset, origin);
}
public override void SetLength(long value)
{
_shrink.SetLength(value);
}
public override void Close()
{
_shrink.Close();
}
public override void Write(byte[] buffer, int offset, int count)
{
// capture the data and convert to string
byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string s = Encoding.Default.GetString(buffer);
// filter the string
s = _filter(s);
// write the data to stream
byte[] outdata = Encoding.Default.GetBytes(s);
_shrink.Write(outdata, 0, outdata.GetLength(0));
}
}
}
The ActionFilter class:
public class WhitespaceFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
var response = filterContext.HttpContext.Response;
response.Filter = new WhiteSpaceFilter(response.Filter, s =>
{
s = Regex.Replace(s, #"\s+", " ");
s = Regex.Replace(s, #"\s*\n\s*", "\n");
s = Regex.Replace(s, #"\s*\>\s*\<\s*", "><");
s = Regex.Replace(s, #"<!--(.*?)-->", ""); //Remove comments
// single-line doctype must be preserved
var firstEndBracketPosition = s.IndexOf(">");
if (firstEndBracketPosition >= 0)
{
s = s.Remove(firstEndBracketPosition, 1);
s = s.Insert(firstEndBracketPosition, ">");
}
return s;
});
}
}
And in the end the usage of above:
[HandleError]
[WhitespaceFilter]
public class HomeController : Controller
{
...
}
Maybe you looking for Meleze.Web
Meleze.Web is a toolbox to optimize ASP.NET MVC 3.0 and MVC 4.0 applications.
It provides HTML, JS and CSS minification of Razor views and caching of the returned pages.
Darin Dimitrov write about it here: ASP.Net MVC Razor Views - Minifying HTML at build time
But I think enabling gzip is better solution, you could read about it here: Minify HTML output from an ASP.Net MVC Application
I don't think that there is any way to achieve that. To avoid the tag soup I usually prefer writing custom helpers:
#using(Html.MyDiv(Model.Name))
{
... put the contents of the div here
}
and here's how the custom helper might look like:
public static class HtmlExtensions
{
private class Div : IDisposable
{
private readonly ViewContext context;
private bool disposed;
public Div(ViewContext context)
{
this.context = context;
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.disposed = true;
context.Writer.Write("</div>");
}
}
}
public static IDisposable MyDiv(this HtmlHelper html, string id)
{
var div = new TagBuilder("div");
if (!string.IsNullOrEmpty(id))
{
div.GenerateId(id);
}
html.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
return new Div(html.ViewContext);
}
}
Alternatively you could also do a tag soup:
<div#Html.Raw(Model.Name != string.Empty ? string.Format(" id=\"{0}\"", Html.AttributeEncode(Model.Name)) : string.Empty)>
</div>
For anybody interested in this, I built a simple HTML minification library that can be used with MVC 5:
https://github.com/tompazourek/RazorHtmlMinifier.Mvc5
It operates in compile-time instead of runtime, so it doesn't add any performance overhead. The minification is very simple (just replaces lots of spaces with one space).
Even with GZIP enabled on top of the minified HTML, it can still reduce the payload size.
Related
Using: C#, MVC 5, IIS 8
I am trying to implement an ActionFilter that will minify html. The basic approach here is to substitute the response's Stream with a custom Stream that writes input into a MemoryStream and then on the Close method minifies the content stored in the MemoryStream and writes out the (minified) content.
The problem I am having is that although the response's type is 'text/html', the content passed to the custom Stream does not look like text or html, it looks like binary. I should add that my site's pages are rendering just fine, so whatever that content is, it's not complete garbage. I added some logging statements to debug, and this is what they look like:
Minification Error | Chunks: 1 | Url: /Login/iFrameLogin.aspx |
Encoding: System.Text.UTF8Encoding | MediaType: text/html | Content:
�
I have also tried turning off dynamic compression on my site, and that made no change either. Does anyone have any ideas why my 'text/html' looks like binary?
FilterAttribute
public class MinifyHtmlFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
HttpContextBase context = filterContext.HttpContext;
HttpRequestBase request = context.Request;
HttpResponseBase response = context.Response;
Encoding encoding = response.ContentEncoding;
string mediaType = response.ContentType;
string currentUrl = request.RawUrl;
var minificationManager = HtmlMinificationManager.Current;
if (response.Filter != null
&& response.StatusCode == 200
&& minificationManager.IsSupportedMediaType(mediaType) //text/html
&& minificationManager.IsProcessablePage(currentUrl))
{
response.Filter = new HtmlMinificationFilterStream(response, minificationManager, currentUrl, encoding, mediaType);
}
}
}
Custom Stream
public class HtmlMinificationFilterStream : Stream
{
private readonly HttpResponseBase _response;
private readonly Stream _stream;
private readonly MemoryStream _cacheStream = new MemoryStream();
private readonly IMarkupMinificationManager _minificationManager;
private readonly string _currentUrl;
private readonly Encoding _encoding;
private readonly string _mediaType;
private int _chunkCount = 0;
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { return 0; }
}
public override long Position
{
get;
set;
}
public HtmlMinificationFilterStream(HttpResponseBase response,
IMarkupMinificationManager minificationManager,
string currentUrl,
Encoding encoding,
string mediaType)
{
_response = response;
_stream = response.Filter;
_minificationManager = minificationManager;
_currentUrl = currentUrl;
_encoding = encoding;
_mediaType = mediaType;
}
public override int Read(byte[] buffer, int offset, int count)
{
return _stream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_stream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
_cacheStream.Write(buffer, 0, count);
_chunkCount++;
}
public override void Flush()
{
_stream.Flush();
}
public override void Close()
{
byte[] cacheBytes = _cacheStream.ToArray();
int cacheSize = cacheBytes.Length;
string content = _encoding.GetString(cacheBytes);
var log = $" | Chunks: {_chunkCount} | Url: {_currentUrl} | Encoding: {_encoding} | MediaType: {_mediaType} | Content: {content}";
IMarkupMinifier minifier = _minificationManager.CreateMinifier();
MarkupMinificationResult minificationResult = minifier.Minify(content, _currentUrl, _encoding, false);
bool isMinified = false;
if (minificationResult.Errors.Count == 0)
{
ExceptionHandler.LogException("MINIFICATION SUCCESS" + log, System.Diagnostics.EventLogEntryType.Warning);
using (var writer = new StreamWriter(_stream, _encoding))
{
writer.Write(minificationResult.MinifiedContent);
}
isMinified = true;
}
else
{
foreach (var error in minificationResult.Errors)
{
ExceptionHandler.LogException("Minification Error" + log + " | " + error.SourceFragment, System.Diagnostics.EventLogEntryType.Warning);
}
}
if (!isMinified)
{
_cacheStream.Seek(0, SeekOrigin.Begin);
_cacheStream.CopyTo(_stream);
}
_cacheStream.SetLength(0);
_stream.Close();
}
}
Turning off compression on IIS (8) fixes the issue.
After much googling and searching, I managed to send an image using multiparsers from android to my WCF service, but ideally, I'd like to send several images at once, instead of calling the method over and over again, since it'd take a lot longer, and add a bunch more overhead.
This is my current code
Android (Taken from code found on here somewhere):
public static String postFile(Bitmap bitmap, String urlString) throws Exception {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(urlString);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 30, bao);
byte[] data = bao.toByteArray();
//filename
String fileName = String.format("File_%d.png",new Date().getTime());
ByteArrayBody bab = new ByteArrayBody(data, fileName);
builder.addPart("image", bab);
final HttpEntity yourEntity = builder.build();
class ProgressiveEntity implements HttpEntity {
#Override
public void consumeContent() throws IOException {
yourEntity.consumeContent();
}
#Override
public InputStream getContent() throws IOException,
IllegalStateException {
return yourEntity.getContent();
}
#Override
public Header getContentEncoding() {
return yourEntity.getContentEncoding();
}
#Override
public long getContentLength() {
return yourEntity.getContentLength();
}
#Override
public Header getContentType() {
return yourEntity.getContentType();
}
#Override
public boolean isChunked() {
return yourEntity.isChunked();
}
#Override
public boolean isRepeatable() {
return yourEntity.isRepeatable();
}
#Override
public boolean isStreaming() {
return yourEntity.isStreaming();
} // CONSIDER put a _real_ delegator into here!
#Override
public void writeTo(OutputStream outstream) throws IOException {
class ProxyOutputStream extends FilterOutputStream {
/**
* #author Stephen Colebourne
*/
public ProxyOutputStream(OutputStream proxy) {
super(proxy);
}
public void write(int idx) throws IOException {
out.write(idx);
}
public void write(byte[] bts) throws IOException {
out.write(bts);
}
public void write(byte[] bts, int st, int end) throws IOException {
out.write(bts, st, end);
}
public void flush() throws IOException {
out.flush();
}
public void close() throws IOException {
out.close();
}
} // CONSIDER import this class (and risk more Jar File Hell)
class ProgressiveOutputStream extends ProxyOutputStream {
public ProgressiveOutputStream(OutputStream proxy) {
super(proxy);
}
public void write(byte[] bts, int st, int end) throws IOException {
// FIXME Put your progress bar stuff here!
out.write(bts, st, end);
}
}
yourEntity.writeTo(new ProgressiveOutputStream(outstream));
}
};
ProgressiveEntity myEntity = new ProgressiveEntity();
post.setEntity(myEntity);
HttpResponse response = client.execute(post);
return getContent(response);
}
public static String getContent(HttpResponse response) throws IOException {
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String body = "";
String content = "";
while ((body = rd.readLine()) != null)
{
content += body + "\n";
}
return content.trim();
}
C# WCF Service method to take it
[WebInvoke(UriTemplate = "UploadPicture/{filename}", Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
public String UploadPicture(string filename, Stream fileStream)
{
WriteLog("Uploading picture...");
try
{
MultipartParser parser = new MultipartParser(fileStream);
if (parser.Success)
{
string fileName = parser.Filename;
string contentType = parser.ContentType;
byte[] fileContent = parser.FileContents;
FileStream fileToupload = new FileStream("\\\\OHS-SUN\\Tracker\\robbie\\" + filename, FileMode.Create);
fileToupload.Write(fileContent, 0, fileContent.Length);
fileToupload.Close();
fileToupload.Dispose();
fileStream.Close();
return "Success !!!";
}
else
{
return "Exception!!!";
}
}
catch (Exception ex)
{
WriteLog("Uploading picture exception: " + ex.Message);
}
return "Picture uploaded!";
}
I'd like to go from sending one image, to sending several, each with 2 text attributes; the filename, and the project number they're associated with. Essentially, both is what I need it for. At the moment, I'm just trying to do put another addPart on to the android bit, but then I don't know how to add metadata to that and I wouldn't know how to parse it based on the name. I'm fine with using any third party libraries, the one I'm using on C# at the moment is already one.
Thanks a lot!
Instead of sending multiple images in one thing, I just stuck it in an asynchronous class and sent them concurrently with a max of 10 at a time until all the images are sent in that particular session. Seems to work fine, the implementation's the same, so I've not had to change any of that, which is good. If anyone would like me to post the code I did to do that, I'd be happy to. It's just small snippets here and there added to the above code, though.
Well, the main bit I added was:
public static class FileUploader extends AsyncTask<UploadFile , Void , String> implements Future<String>
{
#Override
protected void onPreExecute() {
filesUploading ++;
}
#Override
protected String doInBackground(UploadFile... uploadFile)
{
try
{
return postFile(uploadFile[0].file, uploadFile[0].projectNo, uploadFile[0].filename);
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
filesUploading --;
}
#Override
public boolean isDone() {
return AsyncTask.Status.FINISHED == getStatus();
}
}
This allows me to send each image separately, and handle them separately.
I have two classes, none of which I can change in any way:
Class 1: Takes a TextWriter as constructor parameter and uses it as an output stream.
Class 2: Provides a method WriteLine(string).
I need an adapter, such that all the output of Class1 is written to Class2. Therefore I started an adapter which extends TextWriter and buffers incoming text, flushing it to the class2 instance as soon as a new line arrives.
However, there are many and more methods in TextWriter - which should I implement? Output in Class1 is string only.
According to MSDN one should override Write(char) as a minimum, however, this enforces me to do all the \r\n new line handling myself as well...
Q1: Do you know of a better way to reach my goal?
Q2: If no, which TextWriter methods should I override to have minimum implementation effort.
Implementing Write(char) on your TextWriter derived class is all you need to do. If somebody calls WriteLine on your new class, the base class WriteLine method is called. It will do the right thing: call your Write method with the individual \r and \n characters.
Actually, WriteLine(string) looks something like this:
void WriteLine(string s)
{
Write(s);
Write("\r\n");
}
And Write(string) is, in effect:
foreach (char c in s)
{
Write(c);
}
All of the Write methods in TextWriter resolve to something that calls Write(char) in a loop.
You really don't have to implement anything else. Just override Write(char) and plug it in. It will work.
You can override those other methods. Doing so will make your class a little more efficient (faster). But it's not required. I say do the simplest thing you can. Then, if you determine after profiling that your custom writer is too slow, override other methods as necessary.
Here's a minimal TextWriter descendant:
public class ConsoleTextWriter: TextWriter
{
public override void Write(char value)
{
Console.Write(value);
}
public override Encoding Encoding
{
get { return Encoding.Default; }
}
}
If I then write:
using (var myWriter = new ConsoleTextWriter())
{
myWriter.Write("hello, world");
myWriter.WriteLine();
myWriter.WriteLine();
myWriter.WriteLine("Goodbye cruel world.");
myWriter.Write("Fee fie foe foo!");
}
The output is:
hello, world
Goodbye cruel world.
Fee fie foe foo!
I'm adding another answer because I couldn't get the above answer to work!
In my experience, I must override WriteLine() and WriteLine(string) in order for those functions to work.
Here's an example that can be used to write a web page as a long-running task goes on. BTW, the HttpResponse object is from ASP.net MVC.
public class WebConsoleWriter : TextWriter
{
HttpResponseBase Response { get; set; }
bool FlushAfterEveryWrite { get; set; }
bool AutoScrollToBottom { get; set; }
Color BackgroundColor { get; set; }
Color TextColor { get; set; }
public WebConsoleWriter(HttpResponseBase response, bool flushAfterEveryWrite, bool autoScrollToBottom)
{
Response = response;
FlushAfterEveryWrite = flushAfterEveryWrite;
AutoScrollToBottom = autoScrollToBottom;
BackgroundColor = Color.White;
TextColor = Color.Black;
}
public WebConsoleWriter(HttpResponseBase response, bool flushAfterEveryWrite, bool autoScrollToBottom, Color backgroundColor, Color textColor)
{
Response = response;
FlushAfterEveryWrite = flushAfterEveryWrite;
AutoScrollToBottom = autoScrollToBottom;
BackgroundColor = backgroundColor;
TextColor = textColor;
}
public virtual void WritePageBeforeStreamingText()
{
string headerFormat =
#"<!DOCTYPE html>
<html>
<head>
<title>Web Console</title>
<style>
html {{
background-color: {0};
color: {1};
}}
</style>
</head>
<body><pre>";
string backgroundColorHex = ColorTranslator.ToHtml(BackgroundColor);
string foregroundColorHex = ColorTranslator.ToHtml(TextColor);
Response.Write(string.Format(headerFormat, backgroundColorHex, foregroundColorHex));
// Add a 256 byte comment because I read that some browsers will automatically buffer the first 256 bytes.
Response.Write("<!--");
Response.Write(new string('*', 256));
Response.Write("-->");
Response.Flush();
}
public virtual void WritePageAfterStreamingText()
{
Response.Write("</pre></body></html>");
}
public override void Write(string value)
{
string encoded = Encode(value);
Response.Write(encoded);
if (FlushAfterEveryWrite)
Response.Flush();
}
public override void WriteLine(string value)
{
Response.Write(Encode(value) + "\n");
if (AutoScrollToBottom)
ScrollToBottom();
if (FlushAfterEveryWrite)
Response.Flush();
}
public override void WriteLine()
{
Response.Write('\n');
if (AutoScrollToBottom)
ScrollToBottom();
if (FlushAfterEveryWrite)
Response.Flush();
}
private string Encode(string s)
{
return s.Replace("&", "&").Replace("<", "<").Replace(">", ">");
}
public override void Flush()
{
Response.Flush();
}
public void ScrollToBottom()
{
Response.Write("<script>window.scrollTo(0, document.body.scrollHeight);</script>");
}
public override System.Text.Encoding Encoding
{
get { throw new NotImplementedException(); }
}
}
The following example code is a simplified version of a setup we use for ads on our pages where we have to write a "position" number on each ad tag. The position numbers must be sequential from top to bottom starting with 1. The problem is that some of the ad tags are defined in the master/layout page and others are defined within the page markup.
Please see the following example code:
_Layout.cshtml:
<!DOCTYPE html>
<html>
<head><title>Ad Position Test</title></head>
<body>
#Html.SequentialNumber()
#RenderBody()
#Html.SequentialNumber()
</body>
</html>
Index1.cshtml:
#{Layout = "~/Views/Shared/_Layout.cshtml";}
#Html.SequentialNumber()
Index2.cshtml:
#{Layout = "~/Views/Shared/_Layout.cshtml";}
#Html.SequentialNumber()
#Html.SequentialNumber()
Helpers.cs
public static class HtmlHelperExtensions
{
public static HtmlString SequentialNumber(this HtmlHelper html)
{
var tile = (int)(html.ViewData["Tile"] ?? 1);
html.ViewData["Tile"] = tile + 1;
return new HtmlString(tile.ToString());
}
}
The output of this setup for Index1.cshtml is: "2 1 3" and for Index2.cshtml it is: "3 1 2 4". This is of course the result of the RenderBody method being executed before the master content is executed.
So question is, how do you output a correctly ordered sequence of numbers that spans both the master page and the content page such that the output will be "1 2 3" and "1 2 3 4" respectively?
You can achieve this through the cunning use of action filters*
* I would like to preface my answer with the fact that I can't necessarily recommend you actually use the method I am about to post. But I was curious whether this could be done, so I did. I've discussed a more reasonable solution using managed HTTP handlers in IIS7 in the comments below, see also this awesome tutorial.
Because, as you have discovered, nested views are rendered in their nesting order, you can't achieve the required effect during the normal view rendering phase. But after the view has been rendered, there's nothing stopping us from modifying the resulting markup (except perhaps common sense).
So first, let's change the HTML helper to emit some kind of marker we can replace:
public static class HtmlHelperExtensions
{
public static HtmlString SequentialNumber(this HtmlHelper html)
{
//Any sufficiently unique string would do
return ":{ad_sequence}";
}
}
In ASP.NET MVC you can decorate controller classes and controller actions with action filter attributes to execute code before and after the actions have been executed. For example, here we will define an action filter to handle all methods inside the HomeController, and only the Index() action, respectively:
[AdSequencePostProcessingFilter]
public class HomeController : Controller
{
}
public class HomeController : Controller
{
[AdSequencePostProcessingFilter]
public ActionResult Index()
{
return View();
}
}
In ASP.NET MVC 3 we can also have global filters which apply to all controller actions in your application:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalFilters.Filters.Add(new AdSequencePostProcessingFilterAttribute());
}
}
You can decide to apply the filter on individual actions, controllers or globally. Your choice.
Now we'll need to define the AdSequencePostProcessingFilterAttribute filter (based on this caching filter class):
public class AdSequencePostProcessingFilterAttribute : ActionFilterAttribute
{
private Stream _output;
private const string AdSequenceMarker = ":{ad_sequence}";
private const char AdSequenceStart = ':';
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Capture the original output stream;
_output = filterContext.HttpContext.Response.Filter;
filterContext.HttpContext.Response.Flush();
filterContext.HttpContext.Response.Filter = new CapturingResponseFilter(filterContext.HttpContext.Response.Filter);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
//Get the emitted markup
filterContext.HttpContext.Response.Flush();
CapturingResponseFilter filter =
(CapturingResponseFilter)filterContext.HttpContext.Response.Filter;
filterContext.HttpContext.Response.Filter = _output;
string html = filter.GetContents(filterContext.HttpContext.Response.ContentEncoding);
//Replace the marker string in the markup with incrementing integer
int adSequenceCounter = 1;
StringBuilder output = new StringBuilder();
for (int i = 0; i < html.Length; i++)
{
char c = html[i];
if (c == AdSequenceStart && html.Substring(i, AdSequenceMarker.Length) == AdSequenceMarker)
{
output.Append(adSequenceCounter++);
i += (AdSequenceMarker.Length - 1);
}
else
{
output.Append(c);
}
}
//Write the rewritten markup to the output stream
filterContext.HttpContext.Response.Write(output.ToString());
filterContext.HttpContext.Response.Flush();
}
}
We'll also need a sink where we can capture the output:
class CapturingResponseFilter : Stream
{
private Stream _sink;
private MemoryStream mem;
public CapturingResponseFilter(Stream sink)
{
_sink = sink;
mem = new MemoryStream();
}
// The following members of Stream must be overriden.
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { return 0; } }
public override long Position { get; set; }
public override long Seek(long offset, SeekOrigin direction)
{
return 0;
}
public override void SetLength(long length)
{
_sink.SetLength(length);
}
public override void Close()
{
_sink.Close();
mem.Close();
}
public override void Flush()
{
_sink.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _sink.Read(buffer, offset, count);
}
// Override the Write method to filter Response to a file.
public override void Write(byte[] buffer, int offset, int count)
{
//Here we will not write to the sink b/c we want to capture
//Write out the response to the file.
mem.Write(buffer, 0, count);
}
public string GetContents(Encoding enc)
{
var buffer = new byte[mem.Length];
mem.Position = 0;
mem.Read(buffer, 0, buffer.Length);
return enc.GetString(buffer, 0, buffer.Length);
}
}
And voilà, we have the sequence increading in document order :P
You could force the numbers on the SequentialNumber() function that comes before the RenderBody and then start the sequence from there. I guess there is a more generic solution, but cant think of any right now :P
Edit
What about this:
public static class HtmlHelperExtensions
{
public static HtmlString SequentialNumber(this HtmlHelper html,int? sequence)
{
if(sequence!=null)
{
var tile = sequence;
html.ViewData["Tile"] = sequence + 1;
}
else
{
var tile = (int)(html.ViewData["Tile"] ?? 1);
html.ViewData["Tile"] = tile + 1;
}
return new HtmlString(tile.ToString());
}
}
And then
<!DOCTYPE html>
<html>
<head><title>Ad Position Test</title></head>
<body>
#Html.SequentialNumber(1)
#Html.SequentialNumber(2)
#RenderBody()
#Html.SequentialNumber(null)
</body>
#{Layout = "~/Views/Shared/_Layout.cshtml";}
#Html.SequentialNumber(null)
#Html.SequentialNumber(null)
I'm not sure if you can be more generic, somehow you have to tell your helper that one call have more priority that the other.
(If anything here needs clarification/ more detail please let me know.)
I have an application (C#, 2.* framework) that interfaces with a third-party webservice using SOAP. I used thinktecture's WSCF add-in against a supplied WSDL to create the client-side implementation. For reasons beyond my control the SOAP message exchange uses WSE2.0 for security (the thinctecture implementation had to be modified to include the WSE2.0 reference). In addition to the 'normal' data package I attach a stored X509 cert and a binary security token from a previous call to a different web service. We are using SSL encryption of some sort - I don't know the details.
All the necessary serialization/deserialization is contained in the web service client - meaning when control is returned to me after calling the client the entire XML string contained in the SOAP response is not available to me - just the deserialized components. Don't get me wrong - I think that's good because it means I don't have to do it myself.
However, in order for me to have something worth storing/archiving I am having to re-serialize the data at the root element. This seems like a waste of resources since my result was in the SOAP response.
Now for my question:
How can I get access to a 'clear' version of the SOAP response so that I don't have to re-serialize everything for storage/archiving?
Edit- My application is a 'formless' windows app running as a network service - triggered by a WebsphereMQ client trigger monitor. I don't think ASP.NET solutions will apply.
Edit - Since the consensus so far is that it doesn't matter whether my app is ASP.NET or not then I will give CodeMelt's (and by extension Chris's) solution a shot.
You can utilize SoapExtension from existing WSE2.0 framework to intercept the responses from the server.
public class MyClientSOAPExtension : SoapExtension
{
Stream oldStream;
Stream newStream;
// Save the Stream representing the SOAP request or SOAP response into
// a local memory buffer.
public override Stream ChainStream( Stream stream )
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeDeserialize:
// before the XML deserialized into object.
break;
case SoapMessageStage.AfterDeserialize:
break;
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
break;
default:
throw new Exception("Invalid stage...");
}
}
}
At stage of SoapMessageStage.BeforeDeserialize,
You can read the expected data you want from oldstream (e.g. use XmlReader).
Then store the expected data somewhere for yourself to use and also you need
forward the old stream data to the newstream for web service later stage to use the data, e.g. deserialize XML into objects.
The sample of logging all the traffic for the web service from MSDN
Here is an example you can setup using Visual studio web reference to http://footballpool.dataaccess.eu/data/info.wso?WSDL
Basically, you must insert in the webservice call chain a XmlReader spyer that will reconstruct the raw XML.
I believe this way is somehow simpler that using SoapExtensions.
Solution solution was inspired by http://orbinary.com/blog/2010/01/getting-the-raw-soap-xml-sent-via-soaphttpclientprotocol/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Reflection;
using System.Xml;
namespace ConsoleApplication1 {
public class XmlReaderSpy : XmlReader {
XmlReader _me;
public XmlReaderSpy(XmlReader parent) {
_me = parent;
}
/// <summary>
/// Extracted XML.
/// </summary>
public string Xml;
#region Abstract method that must be implemented
public override XmlNodeType NodeType {
get {
return _me.NodeType;
}
}
public override string LocalName {
get {
return _me.LocalName;
}
}
public override string NamespaceURI {
get {
return _me.NamespaceURI;
}
}
public override string Prefix {
get {
return _me.Prefix;
}
}
public override bool HasValue {
get { return _me.HasValue; }
}
public override string Value {
get { return _me.Value; }
}
public override int Depth {
get { return _me.Depth; }
}
public override string BaseURI {
get { return _me.BaseURI; }
}
public override bool IsEmptyElement {
get { return _me.IsEmptyElement; }
}
public override int AttributeCount {
get { return _me.AttributeCount; }
}
public override string GetAttribute(int i) {
return _me.GetAttribute(i);
}
public override string GetAttribute(string name) {
return _me.GetAttribute(name);
}
public override string GetAttribute(string name, string namespaceURI) {
return _me.GetAttribute(name, namespaceURI);
}
public override void MoveToAttribute(int i) {
_me.MoveToAttribute(i);
}
public override bool MoveToAttribute(string name) {
return _me.MoveToAttribute(name);
}
public override bool MoveToAttribute(string name, string ns) {
return _me.MoveToAttribute(name, ns);
}
public override bool MoveToFirstAttribute() {
return _me.MoveToFirstAttribute();
}
public override bool MoveToNextAttribute() {
return _me.MoveToNextAttribute();
}
public override bool MoveToElement() {
return _me.MoveToElement();
}
public override bool ReadAttributeValue() {
return _me.ReadAttributeValue();
}
public override bool Read() {
bool res = _me.Read();
Xml += StringView();
return res;
}
public override bool EOF {
get { return _me.EOF; }
}
public override void Close() {
_me.Close();
}
public override ReadState ReadState {
get { return _me.ReadState; }
}
public override XmlNameTable NameTable {
get { return _me.NameTable; }
}
public override string LookupNamespace(string prefix) {
return _me.LookupNamespace(prefix);
}
public override void ResolveEntity() {
_me.ResolveEntity();
}
#endregion
protected string StringView() {
string result = "";
if (_me.NodeType == XmlNodeType.Element) {
result = "<" + _me.Name;
if (_me.HasAttributes) {
_me.MoveToFirstAttribute();
do {
result += " " + _me.Name + "=\"" + _me.Value + "\"";
} while (_me.MoveToNextAttribute());
//Let's put cursor back to Element to avoid messing up reader state.
_me.MoveToElement();
}
if (_me.IsEmptyElement) {
result += "/";
}
result += ">";
}
if (_me.NodeType == XmlNodeType.EndElement) {
result = "</" + _me.Name + ">";
}
if (_me.NodeType == XmlNodeType.Text || _me.NodeType == XmlNodeType.Whitespace) {
result = _me.Value;
}
if (_me.NodeType == XmlNodeType.XmlDeclaration) {
result = "<?" + _me.Name + " " + _me.Value + "?>";
}
return result;
}
}
public class MyInfo : ConsoleApplication1.eu.dataaccess.footballpool.Info {
protected XmlReaderSpy _xmlReaderSpy;
public string Xml {
get {
if (_xmlReaderSpy != null) {
return _xmlReaderSpy.Xml;
}
else {
return "";
}
}
}
protected override XmlReader GetReaderForMessage(System.Web.Services.Protocols.SoapClientMessage message, int bufferSize) {
XmlReader rdr = base.GetReaderForMessage(message, bufferSize);
_xmlReaderSpy = new XmlReaderSpy((XmlReader)rdr);
return _xmlReaderSpy;
}
}
class Program {
static void Main(string[] args) {
MyInfo info = new MyInfo();
string[] rest = info.Cities();
System.Console.WriteLine("RAW Soap XML response :\n"+info.Xml);
System.Console.ReadLine();
}
}
}
Old thread, but in case others are looking to do this today: these ideas of leveraging SoapExtension or creating 'spy' classes are great, but don't work in .NET Core.
#mting923's suggestion to use IClientMessageInspector approach works in .NET Core 3.1; see here: Get SOAP Message before sending it to the WebService in .NET.
A generated SOAP proxy class is still just a WCF client under the hood, and so the IClientMessageInspector approach works a treat, even for an .NET Core Azure Function calling an older SOAP web service. The following works for me in a .NET Core 3.1 Azure Function:
public class SoapMessageInspector : IClientMessageInspector
{
public string LastRequestXml { get; private set; }
public string LastResponseXml { get; private set; }
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
LastRequestXml = request.ToString();
return request;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
LastResponseXml = reply.ToString();
}
}
public class SoapInspectorBehavior : IEndpointBehavior
{
private readonly SoapMessageInspector inspector_ = new SoapMessageInspector();
public string LastRequestXml => inspector_.LastRequestXml;
public string LastResponseXml => inspector_.LastResponseXml;
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(inspector_);
}
}
And then it can be set up like this:
var client = new ServiceClient();
var soapInspector = new SoapInspectorBehavior();
client.Endpoint.EndpointBehaviors.Add(soapInspector);
After invoking a web service call on the client proxy, soapInspector.LastRequestXml and soapInspector.LastResponseXml will contain the raw SOAP request and response (as strings).
Inspired by jfburdet, I wanted to see if it was possible to directly intercept at stream/byte level rather than reconstructing XML. And it is! See code below:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Services.Protocols;
using System.Xml;
using Test.MyWebReference;
namespace Test {
/// <summary>
/// Adds the ability to retrieve the SOAP request/response.
/// </summary>
public class ServiceSpy : OriginalService {
private StreamSpy writerStreamSpy;
private XmlTextWriter xmlWriter;
private StreamSpy readerStreamSpy;
private XmlTextReader xmlReader;
public MemoryStream WriterStream {
get { return writerStreamSpy == null ? null : writerStreamSpy.ClonedStream; }
}
public XmlTextWriter XmlWriter {
get { return xmlWriter; }
}
public MemoryStream ReaderStream {
get { return readerStreamSpy == null ? null : readerStreamSpy.ClonedStream; }
}
public XmlTextReader XmlReader {
get { return xmlReader; }
}
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
DisposeWriterStreamSpy();
DisposeReaderStreamSpy();
}
protected override XmlWriter GetWriterForMessage(SoapClientMessage message, int bufferSize) {
// Dispose previous writer stream spy.
DisposeWriterStreamSpy();
writerStreamSpy = new StreamSpy(message.Stream);
// XML should always support UTF8.
xmlWriter = new XmlTextWriter(writerStreamSpy, Encoding.UTF8);
return xmlWriter;
}
protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize) {
// Dispose previous reader stream spy.
DisposeReaderStreamSpy();
readerStreamSpy = new StreamSpy(message.Stream);
xmlReader = new XmlTextReader(readerStreamSpy);
return xmlReader;
}
private void DisposeWriterStreamSpy() {
if (writerStreamSpy != null) {
writerStreamSpy.Dispose();
writerStreamSpy.ClonedStream.Dispose();
writerStreamSpy = null;
}
}
private void DisposeReaderStreamSpy() {
if (readerStreamSpy != null) {
readerStreamSpy.Dispose();
readerStreamSpy.ClonedStream.Dispose();
readerStreamSpy = null;
}
}
/// <summary>
/// Wrapper class to clone read/write bytes.
/// </summary>
public class StreamSpy : Stream {
private Stream wrappedStream;
private long startPosition;
private MemoryStream clonedStream = new MemoryStream();
public StreamSpy(Stream wrappedStream) {
this.wrappedStream = wrappedStream;
startPosition = wrappedStream.Position;
}
public MemoryStream ClonedStream {
get { return clonedStream; }
}
public override bool CanRead {
get { return wrappedStream.CanRead; }
}
public override bool CanSeek {
get { return wrappedStream.CanSeek; }
}
public override bool CanWrite {
get { return wrappedStream.CanWrite; }
}
public override void Flush() {
wrappedStream.Flush();
}
public override long Length {
get { return wrappedStream.Length; }
}
public override long Position {
get { return wrappedStream.Position; }
set { wrappedStream.Position = value; }
}
public override int Read(byte[] buffer, int offset, int count) {
long relativeOffset = wrappedStream.Position - startPosition;
int result = wrappedStream.Read(buffer, offset, count);
if (clonedStream.Position != relativeOffset) {
clonedStream.Position = relativeOffset;
}
clonedStream.Write(buffer, offset, result);
return result;
}
public override long Seek(long offset, SeekOrigin origin) {
return wrappedStream.Seek(offset, origin);
}
public override void SetLength(long value) {
wrappedStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count) {
long relativeOffset = wrappedStream.Position - startPosition;
wrappedStream.Write(buffer, offset, count);
if (clonedStream.Position != relativeOffset) {
clonedStream.Position = relativeOffset;
}
clonedStream.Write(buffer, offset, count);
}
public override void Close() {
wrappedStream.Close();
base.Close();
}
protected override void Dispose(bool disposing) {
if (wrappedStream != null) {
wrappedStream.Dispose();
wrappedStream = null;
}
base.Dispose(disposing);
}
}
}
}
The MSDN Library includes example code for obtaining the XML of both the request and the response that you can use to archive it. Obviously you'll have to make some changes since the example stores data in a text file, but it isn't too complicated.