{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[this doc on github](https://github.com/dotnet/interactive/tree/master/samples/notebooks/csharp/Docs)\n", "\n", "# Formatting Outputs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## HTML Formatting\n", "\n", "When you return a value or a display a value in a .NET notebook, the default formatting behavior is normally uses HTML to try to provide some useful information about the object. \n", "\n", "### Enumerables \n", "\n", "If it's an array or other type implementing `IEnumerable`, that might look like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "display(new [] {\"hello\", \"world\"} );\n", "\n", "Enumerable.Range(1, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the same basic structure is used whether you pass the object to the `display` method or return it as the cell's value.\n", "\n", "### Objects\n", "\n", "Similarly to the behavior for `IEnumerable` objects, you'll also see table output for dictionaries, but for each value in the dictionary, the key is provided rather than the index within the collection." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "var dictionary = new Dictionary\n", "{\n", " [\"zero\"] = 0,\n", " [\"one\"] = 1,\n", " [\"two\"] = 2\n", "};\n", "dictionary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The default formatting behavior for other types of objects is to produce a table showing their properties and the values of those properties." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "class Person \n", "{\n", " public string FirstName { get; set; }\n", " public string LastName { get; set; }\n", " public int Age { get; set; } \n", "}\n", "\n", "display(new Person { FirstName = \"Mitch\", LastName = \"Buchannon\", Age = 42} );" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When you have a collection of such objects, you can see the values listed for each item in the collection:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "var groupOfPeople = new [] \n", "{\n", " new Person { FirstName = \"Mitch\", LastName = \"Buchannon\", Age = 42 },\n", " new Person { FirstName = \"Hobie \", LastName = \"Buchannon\", Age = 23 },\n", " new Person { FirstName = \"Summer\", LastName = \"Quinn\", Age = 25 },\n", " new Person { FirstName = \"C.J.\", LastName = \"Parker\", Age = 23 },\n", "};\n", "\n", "display(groupOfPeople);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dictionaries \n", "\n", "Displaying a dictionary will show the items by key rather than index." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "display(groupOfPeople.ToDictionary(p => $\"{p.FirstName}\"));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's try something a bit more complex. Let's look at a graph of objects. \n", "\n", "We'll redefine the `Person` class to allow a reference to a collection of other `Person` instances." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "class Person \n", "{\n", " public string FirstName { get; set; }\n", " public string LastName { get; set; }\n", " public int Age { get; set; } \n", " public List Friends { get; } = new List();\n", "}\n", "\n", "\n", "var mitch = new Person { FirstName = \"Mitch\", LastName = \"Buchannon\", Age = 42 };\n", "var hobie = new Person { FirstName = \"Hobie \", LastName = \"Buchannon\", Age = 23 };\n", "var summer = new Person { FirstName = \"Summer\", LastName = \"Quinn\", Age = 25 };\n", "var cj = new Person { FirstName = \"C.J.\", LastName = \"Parker\", Age = 23 };\n", "\n", "mitch.Friends.AddRange(new [] { hobie, summer, cj });\n", "hobie.Friends.AddRange(new [] { mitch, summer, cj });\n", "summer.Friends.AddRange(new [] { mitch, hobie, cj });\n", "cj.Friends.AddRange(new [] { mitch, hobie, summer });\n", "\n", "var groupOfPeople = new List { mitch, hobie, summer, cj };\n", "\n", "display(groupOfPeople);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's a bit hard to read, right? The defaut formatting behaviors are not always as useful as they might be. In order to give you more control object formatters can be customized from within the .NET notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Customization\n", "\n", "## Registering plain text formatters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's clean up the output above by customizing the formatter for the `Person.Friends` property, which is creating a lot of noise. \n", "\n", "The way to do this is to use the `Formatter` API. This API lets you customize the formatting for a specific type. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "using Microsoft.DotNet.Interactive.Formatting;\n", "\n", "Formatter.Register((person, writer) => {\n", " writer.Write(\"person\");\n", "}, mimeType: \"text/plain\");\n", "\n", "groupOfPeople" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With that in mind, we can make it even more concise by registering a good formatter for `Person`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "Formatter.ResetToDefault();\n", "\n", "Formatter.Register((person, writer) => {\n", " writer.Write(person.FirstName);\n", "}, mimeType: \"text/plain\");\n", "\n", "groupOfPeople" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Registering HTML formatters\n", "\n", "To replace the default HTML table view, you can register a formatter for the `\"text/html\"` mime type. Let's do that, and write some HTML using PocketView." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags;\n", " \n", "Formatter.ResetToDefault();\n", "Formatter.Register>((people, writer) => \n", "{\n", " foreach (var person in people)\n", " {\n", " writer.Write(\n", " span(\n", " b(person.FirstName), \n", " \" \",\n", " i($\"({person.Age} years old and has {person.Friends.Count} friends)\"),\n", " br));\n", " }\n", "}, mimeType: \"text/html\");\n", "\n", "groupOfPeople" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "**_See also_**\n", "* [Displaying output](Displaying%20output.ipynb)\n", "* [HTML](HTML.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "file_extension": ".cs", "mimetype": "text/x-csharp", "name": "C#", "pygments_lexer": "csharp", "version": "8.0" } }, "nbformat": 4, "nbformat_minor": 4 }