Scott Hanselman

Launching your Web 2.0 Site with a Giant Text Box

September 18, 2007 Comment on this post [10] Posted in Musings
Sponsored By

Test1 Editing Home - Windows Internet Explorer Once again, another idea *I* didn't think of. ;) There sure are some clever people on this interweb.

Aaron Swartz has launched Jottit and the homepage is brilliant. No help, no FAQ, just a big text-box and a button.

Once you've entered, the site is a Wiki-like Web-page editor that uses Markdown rather than HTML for it's Markup. The home page is just the hook.

The site is fairly complex and even allows you to claim a subdomain. You'll start with a giant textbox, and before you realize it, you've claimed a domain, chosen colors and you've got a pretty professional looking site.

If you've got a young kid who wants to make a web page, I can't think of a better way for them to start.

Homework? As an aside for the .NET folks here, there is Milan Negovan's Markdown.NET implementation. It can be useful for applications where you want a Rich Text Experience but not a Rich Text Editor. Also, anyone can learn markup languages like Markdown...the WAF (Wife Acceptance Factor) tends to be very high. As a programming exercise, it's a simple format and I'm sure it would be a fun project for an intern to write their own Markdown->HTML or Markdown->PDF or Markdown->Word projects.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Google Presentations is out

September 18, 2007 Comment on this post [7] Posted in Reviews
Sponsored By

Fooling with Google Presentations - Windows Internet Explorer Google updated their Applications suite today with the addition of Google Presentation. It was added to all of docs.google.com including Google Apps For Your Domain, which is what I use for the family's docs.

It's good to know it's there if I might need it, but I'd probably just use Notepad if I needed to present in a pinch.

Cool Things

  • It initially LOOKS just like PowerPoint!
  • You can start a presentation then give folks a URL and they can join up and watch like this: View Presentation.
  • You can chat about the presentation being watched.
  • Great Revisions support - many copies are saved all the time, so you'll never lose anything.
  • Upload a PPT
  • Save as a ZIP file! They'll create a "self-contained" ZIP with a single HTML file and the assets you need to run the presentation using any browser, also Eric Meyer's S5, except with less-pretty auto-generated markup.

Meh Things

  • Can't link to pictures online, have to upload. It would be been cool to allow links to Flickr or Google Photos but that would mess up the whole "self contained" offline story.
  • No spellcheck?
  • No animations, shapes, auto-layouts, wizards, etc.
  • No line spacing? Makes bulleted lists look odd and hard to lay out.
  • Does as little as it can without actually being Notepad.exe
  • It's cool, to be clear, but it's ultra-basic. If you're interested in a PowerPoint-like experience on the web, I'd either use PowerPoint's Save As Html feature (maybe you bought a $60 copy of Office! Insane!) or take a good look at Eric Meyer's S5 XHTML Presentation System (sample).
  • JavaScript errors and other rough spots, particularly when trying to add Hyperlinks to Images.
  • You can upload PPT, but you can't Save As PPT.

It's a nice addition to Google Docs, but the flagship product is still Google Spreadsheets, in my opinion.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

ASMX SoapExtension to Strip out Whitespace and New Lines

September 17, 2007 Comment on this post [4] Posted in ASP.NET | Web Services | XML
Sponsored By

Someone asked...

[I've got] a WebService with a WebMethod of the form.  Very Simple.

[WebMethod]
public XmlNode HelloWorld () {
                XmlDocument document = new XmlDocument();
                document.LoadXml(“<a><b><c><d>Hello World</d></c></b></a>”);
                return document;
}

What comes back is something in the response is something like

<a>
                <b>
                                <c>
                                                <d>Hello World</d>
                                </c>
                </b>
</a>

Where each level of indentation is actually only 2 characters.   I would like it to come back just like it is entered in the LoadXml call [no unneeded whitespace and no unneeded new lines.]

This is an old problem. Basically if you look at SoapServerProtocol.GetWriterForMessage, they...

return new XmlTextWriter(new StreamWriter(message.Stream, new UTF8Encoding(false), bufferSize));

...just make one. You don't get to change the settings. Of course, in WCF this is easy, but this person was using ASMX.

Enter the SoapExtension. In the web.config I'll register one.

<system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="ASMXStripWhitespace.ASMXStripWhitespaceExtension, ASMXStripWhitespace"
             priority="1"
             group="High" />
      </soapExtensionTypes>
    </webServices>
...

And my class will derive from SoapExtension. There's lots of good details in this MSDN article by George Shepard a while back.

Here's my quicky implementation. Basically we're just reading in the stream of XML that was just output by the ASMX (specifically the XmlSerializer that used that XmlTextWriter we saw above) infrastructure.

using System;
using System.IO;
using System.Web.Services.Protocols;
using System.Text;
using System.Xml;

namespace ASMXStripWhitespace
{
    public class ASMXStripWhitespaceExtension : SoapExtension
    {
        // Fields
        private Stream newStream;
        private Stream oldStream;

        public MemoryStream YankIt(Stream streamToPrefix)
        {
            streamToPrefix.Position = 0L;
            XmlTextReader reader = new XmlTextReader(streamToPrefix);

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = false;
            settings.NewLineChars = "";
            settings.NewLineHandling = NewLineHandling.None;
            settings.Encoding = Encoding.UTF8;
            MemoryStream outStream = new MemoryStream();
            using(XmlWriter writer = XmlWriter.Create(outStream, settings))
            {
                do
                {
                    writer.WriteNode(reader, true);
                }
                while (reader.Read());
                writer.Flush();
            }
           
            ////debug
            //outStream.Seek(0, SeekOrigin.Begin);
            ////outStream.Position = 0L;
            //StreamReader reader2 = new StreamReader(outStream);
            //string s = reader2.ReadToEnd();
            //System.Diagnostics.Debug.WriteLine(s);

            //outStream.Position = 0L;
            outStream.Seek(0, SeekOrigin.Begin);
            return outStream;
        }

        // Methods
        private void StripWhitespace()
        {
            this.newStream.Position = 0L;
            this.newStream = this.YankIt(this.newStream);
            this.Copy(this.newStream, this.oldStream);
        }

        private void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }

        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                case SoapMessageStage.AfterDeserialize:
                    return;

                case SoapMessageStage.AfterSerialize:
                    this.StripWhitespace();
                    return;
                case SoapMessageStage.BeforeDeserialize:
                    this.GetReady();
                    return;
            }
            throw new Exception("invalid stage");
        }

        public override Stream ChainStream(Stream stream)
        {
            this.oldStream = stream;
            this.newStream = new MemoryStream();
            return this.newStream;
        }

        private void GetReady()
        {
            this.Copy(this.oldStream, this.newStream);
            this.newStream.Position = 0L;
        }

        public override object GetInitializer(Type t)
        {
            return typeof(ASMXStripWhitespaceExtension);
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return attribute;
        }

        public override void Initialize(object initializer)
        {
            //You'd usually get the attribute here and pull whatever you need off it.
            ASMXStripWhitespaceAttribute attr = initializer as ASMXStripWhitespaceAttribute;
        }
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class ASMXStripWhitespaceAttribute : SoapExtensionAttribute
    {
        // Fields
        private int priority;

        // Properties
        public override Type ExtensionType
        {
            get { return typeof(ASMXStripWhitespaceExtension); }
        }

        public override int Priority
        {
            get { return this.priority; }
            set { this.priority = value;}
        }
    }
}

The order that things happen is important. The overridden call to ChainStream is where we get a new copy of the stream. The ProcessMessage switch is our opportunity to "get ready" and where we "strip whitespace."

If you want a method to use this, you have to add the attribute, in this case "ASMXStripWhitespace" to that method. Notice the attribute class just above. You can pass things into it if you like or override standard properties also.

public class Service1 : System.Web.Services.WebService
{
   [WebMethod]
   [ASMXStripWhitespace]
   public XmlNode HelloWorld () {
         XmlDocument document = new XmlDocument();
         document.LoadXml("<a><b><c><d>Hello World</d></c></b></a>");
         return document;
   }
}

The real work happens in YankIt where we just setup our own XmlSerializer and spin through the reader and writing out to the memory stream using the new settings of no new line chars and no indentation. Notice that the reader.Read() is a Do/While and not just a While. We don't want to lose the root node.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Changing where XmlSerializer Outputs Temporary Assemblies

September 17, 2007 Comment on this post [9] Posted in ASP.NET | Learning .NET | XML | XmlSerializer
Sponsored By

With this tip, I couldn't find any documentation, so you're on your own. That means no help/support from me or anywhere. YMMV - Your Mileage May Vary. No warranties. Enjoy.

Someone asked:

"When using the XmlSerializer from ASP.NET there are permissions issues can can be solved by granting the user account read/write permissions on the %SYSTEMROOT%\Temp folder but I would much rather have the temporary files created in a different folder"

So I poked around. If you want the XmlSerializer to output it's assemblies elsewhere, here's how. Note that I used the previous tip on How To Debug into a .NET XmlSeraizlizer Generated Assembly just to prove this was working. It's not needed for this tip!

To start, let's see where the temporary files end up usually.

First, I'll add this to my web.config (or whatever.exe.config if you like). This is just to visualize and confirm. It's not needed for this tip.

<configuration>
   <system.diagnostics>
      <switches>
         <add name="XmlSerialization.Compilation" value="1" />
      </switches>
   </system.diagnostics>
</configuration>

I'll debug my application, but I'll use SysInternal's Process Monitor and set the filter to only show processes whose name contains the string "WebDev" and see what files the VS WebDev Server writes to. If you're using IIS, you would search for W3WP or ASPNET_WP.

Process Monitor - Sysinternals www.sysinternals.com

Here I can see it writing to C:\Users\Scott\AppData\Local\Temp. The file names are auto-generated...note their names, in my case 6txrbdy.0.*. To prove these are the real generated XmlSerializers, I'll put a breakpoint on my app just before I call CreateSerializer and then drag the .cs file over from the TEMP folder into VS.NET. Note that the .PDB will get loaded when I hit F11 to step into.

XmlSerializerAlternateLocation (Debugging) - Microsoft Visual Studio (Administrator) (2)

Make note of the Call Stack. See the name of the assembly and the name of the .cs file? So, we're sure now where this XmlSerializer came from; it came from C:\Users\Scott\AppData\Local\Temp. If we were running IIS, it'd have been in %SYSTEMROOT%\Temp. The point being, it's automatic and it's temporary and your process needs WRITE access to that folder.

Now, poking around in Reflector with the not-used-often-enough Analyze method shows that XmlSerializerFactory.CreateSerializer eventually ends up in XmlSerializerCompilerParameters.Create which looks for a Configuration Section called “xmlSerializer” and a key called TempFilesLocation.

Lutz Roeders .NET Reflector (3)

That actually means it's looking for a section called System.Xml.Serializer and an element called xmlSerializer and an attribute called tempFilesLocation.

I'll add this to my web.config now:

<system.xml.serialization> 
  <xmlSerializer tempFilesLocation="c:\\foo"/> 
</system.xml.serialization> 

...and create a c:\\foo directory. Make sure your hosting process has write access to the directory.

I'll run my app again, and check out that new folder.

Administrator CWindowssystem32cmd.exe (2)

I've got newly generated XmlSerializer temporary assemblies in there. Undocumented, yes, but it does the job. Note, stuff like this can totally go away at any minute, so don't base your whole design on things like this. It's your tush, not mine, on the line.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Clever: XML to Schema Inference Wizard for Visual Studio 2008

September 15, 2007 Comment on this post [4] Posted in LINQ | Microsoft
Sponsored By

VB9 support for XML is pretty sexy, as I've mentioned before. Specifically, the XML Literals may have me using VB more often, or, more likely, mixing VB9 and C# together. More on that soon.

Here's some C# LINQ to XML:

XDocument booksXML = XDocument.Load(Server.MapPath("books.xml"));        
var books = from book in 
                    booksXML.Descendants("{http://example.books.com}book")
                    select book.Element("{http://example.books.com}title").Value;
Response.Write(String.Format("Found {0} books!", books.Count()));

Here's the same code in VB:

Dim booksXML = XDocument.Load(Server.MapPath("books.xml"))
Dim books = From book In booksXML...<b:book> Select book.<b:title>.Value
Response.Write(String.Format("Found {0} books!", books.Count()))

What's significant is the intellisense for your XML, created via schema, within your VB code.

To make it clearer:

VB9LINQXML

I've just pressed "." after typing "book.<" and I've got intellisense for my XSD-described XML document. Slick.

However, this assumes that you actually HAVE a schema, either having written one, or inferred one. I would hazard to guess that many (most?) projects haven't created an XSD. Heck, I see a lot of XML with no namespace at all! Without an XSD, Visual Basic can't give you this experience.

The teams have just snuck a new Project Item Template up onto Microsoft Download Center for use in Orcas. It's the XML to Schema Inference Wizard. Here's the deal.

You've got some XML somewhere without a schema and you're working on it in a project. You want intellisense for XML in VB9, so you right click on your project and select Add | New Item and see this dialog. (NOTE: you might need to SCROLL DOWN to see it!)

Add New Item - CUsersScottDesktopASP.NET OrcasNew CodeChapter 9VBListing9-10q_app

See the new item, Xml To Schema? Name your new schema and select Add and you'll see this dialog:

Infer XML Schema from XML documents

What's cool here is that you can Add from File, Add from Web or even Paste in an example XML document. You can add as many as you like, then click OK.

The new schemas will be inferred using the XML inference APIs (you could have done this one by one with xsd.exe from the command line, but that can be tedious, and also command-lines tend to squash Joe Public's fun if they are required.) and added to your project.

Now, in your VB code, type "Imports <xmlns:yourprefix=" and you'll get Intellisense with all the namespaces that you want to make available for your LINQ to XML expressions.

ImportsXMLNamespace

And that's it. At this point, you've associated the fully qualified namespace with whatever prefix you chose, and you'll have Intellisense for all your XML within VB9. Note that the inference isn't perfect, as we're "gleaning" a lot, so the more representative of reality your instance document, the better. You're welcome to hand-edit them also if they get to 80% and you want to take the XSD all the way.

Be sure to check out Beth Massi's Blog's LINQ Category for more detail on VB9 and LINQ.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.