My 6 year old started First Grade this last week. We've been reading to him and his brother, without fail, every night for their entire lives. It's been awesome to start seeing their little brains firing as they are starting to read on their own.
My wife picked up a copy of the classic 1950s Dick and Jane series since it's what her parents used and how she learned to read. We only made it a few pages in when both boys declared "this is boring!"
"Why is it boring?"
"There are no light sabers! Dick and Jane don't DOOOOOO anything."
The boys not only wrote all the text but also made all the decisions about the pictures. You can blame me for the poor Photoshop work but the kids get the credit for everything else. We printed it on glossy inkjet paper and stapled it and the boys are THRILLED.
Have fun. PDF at the bottom, thumbnails here.
NOTE: This is a parody, it's not for sale. Don't sue me. Just playin'.
We had so much fun doing this. I hope my kids end up feeling empowered to create and remix.
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.
When the Visual Studio 2012 free versions were originally announced the first thing I noticed was that they had switched from a language-specific model (C# Express, VB Express, etc) to a target-specific model (Windows 8, Web). However, I was very surprised (and disappointed) that there was no free way to make Console Apps, or Windows Forms apps, or anything for the Windows Desktop. I wasn't the only one who thought this was a problem. Shortly thereafter (with a lot of people pushing) Soma announced there would be a "Windows Desktop" Express version for free. He said:
...we heard from our community that developers want to have for Windows desktop development the same great experience and access to the latest Visual Studio 2012 features at the Express level.
Today, I’m happy to announce that we will add Visual Studio Express 2012 for Windows Desktop to the Visual Studio 2012 family. This will bring to the Visual Studio Express family significant new capabilities that we’ve made available in Visual Studio 2012 for building great desktop applications.
I'm glad folks realized that no-cost desktop software development is important. Open Source projects need free tools like the Express SKUs. Even better that the the Express Desktop SKU gets the new 2012 features as well.
Today Visual Studio has made Visual Studio Express 2012 for Windows Desktop available and you can go download it now free. The best part is that this one SKU supports C++, C#, and Visual Basic together. With this one free version you can make WinForms, WPF, Console or Class Libraries with any or all of Visual Basic, C#, as well as Win32 projects, class libraries, and CLR apps using C++. You can also, of course, combine projects over multiple languages into a single solution. You can target both .NET 4.0 and 4.5.
NOTE: You might wonder, what about a free F#? Why isn't F# included? We've got a free download to add F# support to the free Visual Studio Express 2012 for Web!
While Express SKUs don't allow arbitrary add-ins (you need Pro for that) the free SKU does include Unit Testing, Code Analysis, as well as the NuGet package manager. It's a bit of a nice coup for my little group that NuGet is now included in ALL Visual Studio 2012 SKUs, even Express ones. Package management is finally happening in .NET.
In the screenshot below I've added a C++ Console app, a Window Forms C# app and a C# Console to a single solution in VS2012 using Express for Windows Desktop.
Here's a simple Windows Service with a basic heartbeat timer using Topshelf:
public class TownCrier { readonly Timer _timer; public TownCrier() { _timer = new Timer(1000) {AutoReset = true}; _timer.Elapsed += (sender, eventArgs) => Console.WriteLine("It is {0} an all is well", DateTime.Now); } public void Start() { _timer.Start(); } public void Stop() { _timer.Stop(); } }
public class Program { public static void Main() { HostFactory.Run(x => //1 { x.Service<TownCrier>(s => //2 { s.ConstructUsing(name=> new TownCrier()); //3 s.WhenStarted(tc => tc.Start()); //4 s.WhenStopped(tc => tc.Stop()); //5 }); x.RunAsLocalSystem(); //6
Topshelf even has nice Log4net and NLog integration. Anyway, this is just the kind of powerful, useful, and interesting open source library that could be helped by a free Express SKU for Desktop. I'm not involved directly (yet ;) ) in making decisions this high up, but I (and many, many others) inside and out continue to advocate for balance. In this case I'm very glad that the decision was made to ship this SKU and I hope you all find it useful whether you work in open source or in education.
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.
Modeling Binding and EditorTemplates...for ASP.NET Web Forms?
DisplayTemplates and EditorTemplates are a great way in ASP.NET MVC to keep things DRY (Don't Repeat Yourself.) That means I can just write EditorFor() calls like this:
@Html.EditorFor(model => model.Location)
See how I didn't say "TextBoxFor" or "MapFor"? You say EditorFor and it makes the right choice. If the type is called DbGeography then it will look for a Editor Template at ~/Shared/EditorTemplates/DbGeography.cshtml. It's a nice feature of ASP.NET MVC that folks don't use enough.
Now, remember ASP.NET Dynamic Data? You might think that idea "died" or was "retired" when actually the concepts are built into ASP.NET itself. That means that ASP.NET Web Forms developers can have "Editor Templates" as well. They are called FieldTemplates in ASP.NET Web Forms parlance, and making sure we have feature parity like this is part of the larger move towards One ASP.NET. We'll take the ASP.NET MVC sample using DbGeography and make it work for Web Forms in a very similar way.
<%-- Let's not do this: <asp:TextBox ID="location" runat="server" Text="<%# BindItem.Location %>"></asp:TextBox>--%> <asp:DynamicControl runat="server" ID="Location" DataField="Location" Mode="Insert" />
When we do a POST, ModelBinders handle the boring work of digging types out of the HTTP POST. These work in not just MVC but also Web Forms and Web API now. Rather that Request["this"] and Request["that"] a model binder can be registered to do the work of populating a type from the Request. Even better, we can populate objects not only from the POST but also anywhere that provides values including Cookies, QueryStrings and more.
Let's walk through this one by one and at the end we'll have a complete sample that has:
ASP.NET Web Forms and AS.NET MVC in one application, living together.
FriendlyURLs for Web Forms and Routing for MVC
90% Shared Model Binding Code between Web Forms and MVC
Spatial types custom DbGeography Model Binder
Simple CRUD (Create, Read, Update, Delete) to the same database in both Web Forms and MVC using the same model.
The goal is to continue to move towards a cleaner, more unified platform...One ASP.NET. This is an example. Thanks to Pranav for his help!
Here's a FormView in ASP.NET Web Forms. Notice the ItemType is set, rather than using Eval(). We're also using SelectMethod rather than an ObjectDataSource.
The FormView doesn't specify what a location or name should look like, but since we know the model...
public class TouristAttraction { public int TouristAttractionId { get; set; } public string Name { get; set; } public DbGeography Location { get; set; } }
...it will dynamically figure out the controls (hence, DynamicControl) and find FieldTemplates in the DynamicData folder:
Those templates are simple. Here's the Edit example.
<%@ Control Language="C#" CodeBehind="DbGeography_Edit.ascx.cs" Inherits="MvcApplication2.DynamicData.FieldTemplates.DbGeography_EditField" %>
You might say, hang on, this is just a text box! I thought we weren't using TextBoxes? The point is that we have control in a single place over what a DbGeography - or any type - looks like when it's being edited, or when it's read-only. In this example, I AM using a Textbox BUT I've added a CssClass that I will use to create a Google Map using obtrusive JavaScript thanks to my recent refactoring from Dave Ward. If I wanted I could change this FieldTemplate to be a 3rd party control or whatever custom markup I want.
If you have an object called Foo, then make a Foo.ascx and Foo_Edit.ascx and put them in ~/DynamicData/FieldTemplates and they'll be used by DynamicControl.
Let me first say that Model Binding between ASP.NET Web Forms, MVC and Web API isn't unified. I want more unification and I am continuing to push the One ASP.NET vision internally and many people share that goal.
In the previous blog post on ASP.NET MVC, Model Binding and DbGeography I already had a good Model Binder that I want to reuse between MVC and Web Forms. I can do it, although the ModelBinderProvider stuff isn't very well unified.
First, here's the unified Model Binder for DbGeography that is used for both MVC and Web Forms. We implement two interfaces and use one implementation. Not ideal, but it works.
public class DbGeographyModelBinder : IMvcModelBinder, IWebFormsModelBinder { public object BindModel(ControllerContext controllerContext, MvcModelBindingContext bindingContext) { var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); return BindModelImpl(valueProviderResult != null ? valueProviderResult.AttemptedValue : null); }
private DbGeography BindModelImpl(string value) { if (value == null) { return (DbGeography)null; } string[] latLongStr = value.Split(','); // TODO: More error handling here, what if there is more than 2 pieces or less than 2? // Are we supposed to populate ModelState with errors here if we can't conver the value to a point? string point = string.Format("POINT ({0} {1})", latLongStr[1], latLongStr[0]); //4326 format puts LONGITUDE first then LATITUDE DbGeography result = DbGeography.FromText(point, 4326); return result; } }
Part of the "trick" are these namespace aliases:
using IMvcModelBinder = System.Web.Mvc.IModelBinder; using IWebFormsModelBinder = System.Web.ModelBinding.IModelBinder;
using MvcModelBindingContext = System.Web.Mvc.ModelBindingContext; using WebFormsModelBindingContext = System.Web.ModelBinding.ModelBindingContext;
I'd love to see unification of the Model Binding stack at some point.
In Web Forms we could register a single model binder for a single type like this:
ModelBinderProviders.Providers.RegisterBinderForType(typeof(DbGeography), new DbGeographyModelBinder());
Or collect a collection of like types into a Provider of Binders and add them like this:
I only have one Model Binder but here's how I'd register a provider for both Web Forms and MVC and have them use my same binder if I wanted:
public class EFModelBinderProviderMvc : System.Web.Mvc.IModelBinderProvider { public IMvcModelBinder GetBinder(Type modelType) { if (modelType == typeof(DbGeography)) return new DbGeographyModelBinder(); return null; } }
public class EFModelBinderProviderWebForms : System.Web.ModelBinding.ModelBinderProvider { public override IWebFormsModelBinder GetBinder(ModelBindingExecutionContext modelBindingExecutionContext, WebFormsModelBindingContext bindingContext) { if (bindingContext.ModelType == typeof(DbGeography)) return new DbGeographyModelBinder(); return null; } }
Or I could enable FriendlyUrls after my MVC routes like this:
//MVC will be for MVC, while WebForms is under /WebForms/ using Friendly URLs routes.MapRoute( name: "Default", url: "MVC/{controller}/{action}/{id}", defaults: new { controller = "Attraction", action = "Index", id = UrlParameter.Optional } );
routes.EnableFriendlyUrls();
Here's what my site looks like now. Notice the /MVC and /WebForms URLs. I can call /WebForms/Create or /MVC/Create..
I generate the FriendlyUrls like this in Web Forms:
If this was a larger app I would write better helper methods for both, perhaps using an open source helper library.
UPDATE: One thing I forgot to mention was how to get the values out of the FriendlyURL. You can use things like [Form] and [QueryString] to model bind in WebForms. Now you can add [FriendlyUrlSegments] to get data out, like the ID in this example:
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.
It's nice to have your things backed up to the cloud, but you really need to have local backups as well. I have two 1TB pocket hard drives that I rotate between my home and the bank. They are labeled Offsite Backup A and Offsite Backup B. You can encrypt them with either Bitlocker To Go or TrueCrypt, and I do.
I've got years and years of email in my only personal email account, powered by Gmail. I've recently started backing up my WHOLE gmail account with a wonderful free tool called GMVault. Setup requires a little attention to detail but once it's done, it's done.
Once installed, you run GMVault-Shell and type "gmvault sync youremail@address.com." The first backup will take HOURS and on Windows will put thousands and thousands of files in your C:\Users\YOURNAME\gmvault-db directory. You can move this directory if you want. My email backup was over 350,000 emails so I moved it to my larger D drive by using the -d option on the command line.
After this multi-hour sync was finally done, I wanted to make sure I updated the archive every week or so with backups of new emails.
Create a Scheduled Gmail Backup with Task Scheduler
Go to your start menu and type "Task" and run the Task Scheduler. Some folks don't even know this exists!
On the right side click "Create Basic Task."
Make it weekly or monthly or whatever makes you happy.
Your action is Start a Program
Make the Program like this and check your path first.
I also made my task start in the same directory as GmVault, so "C:\Users\YOURNAME\AppData\Local\gmvault"
My scheduled task ended up with command line arguments like this:
sync -t quick scott@myemail.com -d D:\gmvault-db
You can test it by right clicking on it in the Task Scheduler list and clicking "Run." If you need to debug it or if it just starts and then quickly disappears, go into your gmvault.bat and add a "pause" command before the "exit" command to keep the window open long enough to see any errors.
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.
Let's solve both these problems with a new ASP.NET feature just pre-released today in alpha form on NuGet. My peer Damian Edwards and developer Levi Broderick along with QA by Pranav and Anton have come up with a pretty awesome solution based on the original "Smarty Routes" idea from Eilon Lipton and the result is FriendlyUrls.
NOTE: If you've been paying attention to ASP.NET for the last few months you'll recognize this incremental useful but appropriately sized forward motion as being all part of the One ASP.NET master plan.
It's also worth noting that this FriendlyUrls NuGet package includes BOTH an ASP.NET 4.5 and ASP.NET 4 version so .NET 4 folks get love too.
FriendlyUrls Hello World Example
First, the obvious example. Bring up Visual Studio and File | New Project | New ASP.NET Web Forms Application. Now, from the Package Manager Console or from Manage NuGet Packages, install Microsoft.AspNet.FriendlyUrls. You'll need to "Include Prerelease" packages with -pre from the command line or via the dropdown in the UI.
Be sure to read the readme.txt that pops up as you'll need to ensure that the FriendlyUrls routing gets called on application startup! I added this one line to my Application_Start:
RouteConfig.RegisterRoutes(RouteTable.Routes);
Here's the cool part. If I hit one of my existing links, like Contact.aspx, look what happened. See how the GET request for /Contact.aspx turned into a 301 redirect to /Contact?
If you have a Web Form called /Foo.aspx, you automatically get a /Foo route and can call your page like that! Hence, Microsoft.AspNet.FriendlyUrls.
Just by adding the one package and calling
routes.EnableFriendlyUrls();
in RouteConfig (this default came down with the NuGet package) my whole WebForms app loses its .ASPX extensions and gets reasonable defaults.
FriendlyUrls Advanced Sample
Get it? Ok, let's dig into some of the obvious next questions and some more advanced scenarios. How do I get values out of the URL? I'm used to Request.QueryString and Request.Form, but how do I get ahold of these URL segments?
Here's a Foo.aspx that I've visited via /Foo.
If I click "Click Me" the URL points to /Foo/bar/34.
NOTE: Be aware of the magic. It makes sense. If there was a 34.aspx in a folder called Bar in a folder called Foo, we would have used that file. There wasn't. If there was a file called Bar.aspx in a folder called Foo we would have used that. There wasn't. So, we used Foo.aspx and passed in the rest of the URL.
UPDATE: One thing I forgot to mention was how to get the values out of the FriendlyURL. You can use things like [Form] and [QueryString] to model bind in WebForms. Now you can add [FriendlyUrlSegments] to get data out, like the ID in this example:
When you bring down the NuGet package you'll also get a Site.Mobile.Master. If I visit them with the Electric Plum Mobile Simulator (iPhone) I see a default mobile page, automatically.
Ah, you see where this is going. I'll copy Foo.aspx to Foo.Mobile.aspx. I'll make a small change. I'll visit /Foo/bar/34 again except now I get the mobile master and the mobile foo, automatically.
What I want to support switching back and forth from Desktop to Mobile? Just add a ViewSwitcher control, also included.
<friendlyUrls:ViewSwitcher runat="server" />
Now I re-render and I get a "switch to mobile" and switch to desktop.
Now I can go back and forth between views and request a desktop site even when on mobile.
So basic mobile is nice but I might want very specific mobile views for iPhone, iPad, Opera Mobile, etc.
Super Advanced Mobile Routes for Specific Devices with ASP.NET FriendlyUrls
By default FriendlyUrls uses a class called WebFormsFriendlyUrlResolver but you can derive from this class and change its behavior however you like. Here's an example of a "DeviceSpecificWebFormsFriendlyUrlResolver" or, better yet, Mobile Friendly Urls for WebForms.
This derived URL resolver does just that, it resolves URLs to physical Web Forms pages. You'd then pass it into the overload of EnableFriendlyUrls(...);
IMPORTANT NOTE: This code is just a very early sample, there will be a more complete one released later.
public class DeviceSpecificWebFormsFriendlyUrlResolver : WebFormsFriendlyUrlResolver { private readonly IDictionary<string, string> _deviceUserAgentMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "Opera Mobi", "OperaMobile" }, { "iPhone", "iPhone" }, { "iPad", "iPad" } };
protected override IList<string> GetExtensions(HttpContextBase httpContext) { var extensions = base.GetExtensions(httpContext).ToList(); if (extensions.Contains(MobileAspxExtension, StringComparer.OrdinalIgnoreCase)) { // Base has determined we should look for a mobile page, let's add device specific // extension to the beginning. var deviceSpecificSufffix = GetDeviceSpecificSuffix(httpContext); if (!String.IsNullOrEmpty(deviceSpecificSufffix)) { extensions.Insert(0, "." + deviceSpecificSufffix + AspxExtension); } } return extensions; }
protected override bool TrySetMobileMasterPage(HttpContextBase httpContext, Page page, string mobileSuffix) { var deviceSpecificSufffix = GetDeviceSpecificSuffix(httpContext); if (!String.IsNullOrEmpty(deviceSpecificSufffix) && base.TrySetMobileMasterPage(httpContext, page, deviceSpecificSufffix)) { // We were able to set a device specific master page, so just return return true; }
// Just use the base logic return base.TrySetMobileMasterPage(httpContext, page, mobileSuffix); }
private string GetDeviceSpecificSuffix(HttpContextBase httpContext) { foreach (var item in _deviceUserAgentMap) { if (httpContext.Request.UserAgent.Contains(item.Key, StringComparison.OrdinalIgnoreCase)) { return item.Value; } }
return String.Empty; } }
Now we've created a map of device specific suffixes, so we can have not Foo.Mobile.aspx, but rather Foo.iPhone.aspx and Foo.OperaMobile.aspx, etc.
Here's a little demo that loads a bunch of names into a list. Here's /async, the desktop view.
Now we'll add jQuery mobile to the mobile master page, and use it on the mobile version of the same page. We're still calling the same data source and reusing all that code.
I'm pretty jazzed about what this means for ASP.NET and Web Forms developers. We're going to continue to push forward and improve ASP.NET even now, after Visual Studio 2012 has been released. Sometimes we'll add small features via NuGet packages, sometimes editor improvements as free VSIX Extensions like the Web Essentials playground for 2012 and larger changes via released updates to all of ASP.NET. I hope you like the direction we're heading.
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.