Scott Hanselman

Exploring ASP.NET Web Pages - A fully-featured MiniBlog using just Razor

April 16, '14 Comments [23] Posted in ASP.NET | Open Source
Sponsored By

ASP.NET "Razor" Web Pages are ASP.NET sites without models, views, controllers, or project files. Some folks say "oh, that's just Classic ASP, or PHP right? Not at all. It's the full power and speed of the .NET CLR, the full syntax of C#, LINQ, along with things like C# dynamics. It's super powerful, and my friend Mads and I are surprised more people don't use them for small things.

In fact, Rob Conery and I did the http://thisdeveloperslife.com web site using just Razor and Rob's "massive" micro-ORM. Later I made http://hanselminutes.com with Web Pages as well.

This blog runs DasBlog, an older ASP.NET 2.0 blogging engine I worked on with Clemens Vasters and a lot of co-contributors, but I'm actively checking on Mads' MiniBlog, a minimal but VERY competent blog engine using Razor Web Pages. Why wouldn't I use something like Ghost? I've thought about it, but MiniBlog is SO minimal and that makes it very attractive.

Here's some things I like about MiniBlog, as both a blog and a learning tool.

Minimal

It's not called Mini for fun. There's a truly minimal packages.config of dependencies:

<packages>
<package id="AjaxMin" version="5.2.5021.15814" targetFramework="net45" />
<package id="Microsoft.AspNet.Razor" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
<package id="xmlrpcnet" version="3.0.0.266" targetFramework="net45" />
<package id="xmlrpcnet-server" version="3.0.0.266" targetFramework="net45" />
</packages>

Clean use of Handlers for Web Services

Blogs do more than just serve pages, there is also a need for RSS feeds, MetaWeblog Web Services for things like Windows Live Writer, and dynamic minification for JS and CSS.

<handlers>
<add name="CommentHandler" verb="*" type="CommentHandler" path="/comment.ashx"/>
<add name="PostHandler" verb="POST" type="PostHandler" path="/post.ashx"/>
<add name="MetaWebLogHandler" verb="POST,GET" type="MetaWeblogHandler" path="/metaweblog"/>
<add name="FeedHandler" verb="GET" type="FeedHandler" path="/feed/*"/>
<add name="CssHandler" verb="GET" type="MinifyHandler" path="*.css"/>
<add name="JsHandler" verb="GET" type="MinifyHandler" path="*.js"/>
</handlers>

MiniBlog uses .ashx (HttpHanders) and wires them up in web.config. RSS feeds are easily handled with System.ServiceModel.Syndication, even JavaScript and CSS minification. Though MiniBlog is very new, it uses the old but extremely reliable CookComputing.XmlRpc for the MetaWeblog service communication with Windows Live Writer. I

No Database Need

I like apps that can avoid using databases. Sometimes the file system is a fine database. I thought this when we worked on DasBlog, Mads thought it when he made BlogEngine.NET (his original blog engine) and that "no database needed" design tenet continues with MiniBlog. It stores its files in XML, but MiniBlog could just as easily use JSON.

Clean Content-Editable Design Service

I always (exclusively) use Windows Live Writer for my blog posts. WLW is also the preferred way to write posts with MiniBlog. However, if you insist, MiniBlog also has a really nice content-editable scheme with a great toolbar, all in the browser:

Nice Editing Experience

When you are viewing a post while logged in as Admin, you click Edit and turn the page into editable content.

editPost = function () {
txtTitle.attr('contentEditable', true);
txtContent.wysiwyg({ hotKeys: {}, activeToolbarClass: "active" });
txtContent.css({ minHeight: "400px" });
txtContent.focus();

btnNew.attr("disabled", true);
btnEdit.attr("disabled", true);
btnSave.removeAttr("disabled");
btnCancel.removeAttr("disabled");
chkPublish.removeAttr("disabled");

showCategoriesForEditing();

toggleSourceView();

$("#tools").fadeIn().css("display", "inline-block");
}

The resulting HTML you write (in a WYSIWYG mode) is converted into XHTML and posted back to MiniBlog:

parsedDOM = ConvertMarkupToValidXhtml(txtContent.html());

$.post("/post.ashx?mode=save", {
id: postId,
isPublished: chkPublish[0].checked,
title: txtTitle.text().trim(),
content: parsedDOM,
categories: getPostCategories(),
})

The JavaScript is surprisingly simple, and gets one thinking about adding basic editing and CMS functions to websites. A design mode would be a daunting task 5 years ago, and with today's JavaScript it's almost trivial.

It even automatically optimizes images you drag and drop into the design surface and upload.

public static string SaveFileToDisk(byte[] bytes, string extension)
{
string relative = "~/posts/files/" + Guid.NewGuid() + "." + extension.Trim('.');
string file = HostingEnvironment.MapPath(relative);

File.WriteAllBytes(file, bytes);

var cruncher = new ImageCruncher.Cruncher();
cruncher.CrunchImages(file);

return VirtualPathUtility.ToAbsolute(relative);
}

The code is fun to read, and you can go check it out at https://github.com/madskristensen/MiniBlog. It supports HTML5 microdata, sitemaps, both RSS and Atom, simple theming, and gets a 100/100 of Google Page Speed.


Sponsor: Big thanks to RedGate for sponsoring the feed this week. 24% of database devs don’t use source control. Do you? Database source control is now standard. SQL Source Control is an easy way to start - it links your database to any source control system. Try it free!

About Scott

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

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Adding Two-Factor authentication to an ASP.NET application

April 8, '14 Comments [15] Posted in ASP.NET | ASP.NET MVC
Sponsored By
German Lorenz cipher machine by Timitrius used under CC Attributin

ASP.NET Identity 2.0 was released last month and it's got a number of significant updates and new features that are worth checking out. For historical context, read the "Introduction to ASP.NET Identity" article that includes a lot of background and information on why certain decisions were made, as well as an  overview of some of the goals of ASP.NET Identity 2.0 like:

  • One Identity system for ASP.NET Web Forms, MVC, Web API, and Web Pages
  • Total control over user profile schema.
  • Pluggable storage mechanisms from Windows Azure Storage Table Service to NoSQL databases
  • Unit Testable
  • Claims-based Auth adds more choice over simple role membership
  • Social Logins (MSFT, FB, Google, Twitter, etc)
  • Based on OWIN middleware, ASP.NET Identity has no System.Web dependency

You can watch a video of Pranav Rastogi and I upgrading the ASP.NET Membership systems on an older ASP.NET application to the latest bits. There's also migration docs in detail:

ASP.NET Identity is on CodePlex today (and soon to be open sourced...paperwork) at https://aspnetidentity.codeplex.com/ or access the NuGet feed for nightly builds.

Adding Two-Factor authentication to an ASP.NET application

I recently changed all my accounts online to two-factor auth, and I really recommend you do as well. Here's how to add Two-Factor Auth to an ASP.NET application using Identity 2.0.

You'll have a class that is a UserManager that handles access to users and how they are stored. Inside this manager there's an IIdentityMessageService that you can implement to validate a user with whatever you want, like email, SMS, or a time-based token.

Send Verification Code

Here's an example SmsService where I'm using Twilio to send text messages. Again, you can do whatever you want in your implementation.

public class SmsService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
// Plug in your sms service here to send a text message.
message.Destination = Keys.ToPhone; //your number here
var twilio = new TwilioRestClient(Keys.TwilioSid, Keys.TwilioToken);
var result = twilio.SendMessage(Keys.FromPhone, message.Destination, message.Body);

return Task.FromResult(0);
}
}

If I were sending an EmailMessage, I'd do something like this. Note it's just another implementation of the same simple interface:

public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
string text = message.Body;
string html = message.Body;
//do whatever you want to the message
MailMessage msg = new MailMessage();
msg.From = new MailAddress("scott@hanselman.com");
msg.To.Add(new MailAddress(message.Destination));
msg.Subject = message.Subject;
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

SmtpClient smtpClient = new SmtpClient("smtp.whatever.net", Convert.ToInt32(587));
System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(Keys.EmailUser, Keys.EMailKey);
smtpClient.Credentials = credentials;
smtpClient.Send(msg);

return Task.FromResult(0);
}
}

In your IdentityConfig.cs you can register as many TwoFactorProviders as you'd like. I'm adding both Email and Sms here. They include token providers but again, everything is pluggable.

manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser> {
MessageFormat = "Your security code is: {0}"
});
manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser> {
Subject = "SecurityCode",
BodyFormat = "Your security code is {0}"
});

manager.EmailService = new EmailService();
manager.SmsService = new SmsService();

If a user tries to login you need to make sure they are a VerifiedUser. If not, get a valid two factor provider and send them a code to validate. In this case, since there are two providers to choice from, I let them pick from a dropdown. Here's the POST to /Account/SendCode:

public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
// Generate the token and send it
if (!ModelState.IsValid)
{
return View();
}

if (!await SignInHelper.SendTwoFactorCode(model.SelectedProvider))
{
return View("Error");
}
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl });
}

The sender of the two factor code depends on your implementation, of course.

public async Task<bool> SendTwoFactorCode(string provider)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == null)
{
return false;
}

var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);
// See IdentityConfig.cs to plug in Email/SMS services to actually send the code
await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);
return true;
}

When it's time to get the code from them, they need to have logged in with name and password already, and we're now checking the code:

[AllowAnonymous]
public async Task<ActionResult> VerifyCode(string provider, string returnUrl)
{
// Require that the user has already logged in via username/password or external login
if (!await SignInHelper.HasBeenVerified())
{
return View("Error");
}
var user = await UserManager.FindByIdAsync(await SignInHelper.GetVerifiedUserIdAsync());
return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl });
}

We can sign users potentially a number of ways, like with External Sign Ins (Twitter, etc) but here's the TwoFactorSignIn

public async Task<SignInStatus> TwoFactorSignIn(string provider, string code, bool isPersistent, bool rememberBrowser)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByIdAsync(userId);
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
{
// When token is verified correctly, clear the access failed count used for lockout
await UserManager.ResetAccessFailedCountAsync(user.Id);
await SignInAsync(user, isPersistent, rememberBrowser);
return SignInStatus.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user.Id);
return SignInStatus.Failure;
}

If you want this blog post's sample code, make an EMPTY ASP.NET Web Application and run this NuGet command from the Package Manager Console

Install-Package Microsoft.AspNet.Identity.Samples -Pre

Have fun!

Related Links

* Photo of German Lorenz cipher machine by Timitrius used under CC Attribution 


Sponsor: Big thanks to Novalys for sponsoring the blog feed this week! Check out their security solution that combines authentication and user permissions. Secure access to features and data in most applications & architectures (.NET, Java, C++, SaaS, Web SSO, Cloud...). Try Visual Guard for FREE.

About Scott

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

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Back to Basics: Assert your assumptions and diff your source code

March 13, '14 Comments [20] Posted in ASP.NET | Back to Basics
Sponsored By

I've done a whole series of "Back to Basics" posts that I encourage you to check out. Sometimes I'll do a post as a result of a reader emailing me for help and this is one such post.

A person emailed me with an ASP.NET app was behaving differently on his computer vs. another developer's computer.

On his machine when he hit a protected page foo.aspx?returnurl=http://mymachine.domain.com

he would get a FORM element like this:

<form action="foo.aspx?returnurl=http://mymachine.domain.com">

but on everyone else's machines their HTML was:

<form action="foo.aspx">

They debugging and were frustrated and eventually reached out. They said:

1. there's nothing going on in the aspx of login.aspx that would append the querystring.

2. there's nothing going on in the code-behind of the aspx that manipulates Form.Action or messes with the Page.Render in any way.

So, I'm stumped, because the querystring is included on my machine, but not on others. I've tried comparing IIS components, web.config differences, application pool runtime type, machine.config differences, possible differences in Modules for IIS (IISrewrite), but nothing is giving me love. 

I suggested that they assert assumptions and start diffing everything. You can see in the last paragraph that they're comparing stuff but I think you really have to diff everything.

When something "works here but not there" my answer is always, what has changed? What's different? If the answer is "nothing is different" I'm just gonna say it again:

"What's different?"

What are some things we can check?

  • Code
    • Do you know what's on disk?
    • Do you know what ended up in memory? These are different things.
  • Configuration
    • There's local and machine-wide config to check
  • Network Traffic
    • This is often overlooked. The Internet is not a black box, but you'd be surprised how few people hook up a packet sniffer or even just Fiddler to look at HTTP traffic.
    • I've talked to developers who have said "well, that's under SSL so I can't see it." Oh, my friend, if you only knew.

I had them do a sniff and see if there was a difference in HTTP traffic. My assumption was that the HTTP_REFERER HTTP header was different and there was some code that was causing the page to render differently.

We went back and forth over a few days and my reader became frustrated and just added this line in their app's Page_Load:

this.Form.Action = Request.Url.ToString();

Here they are basically reasserting the Form action by pulling it from the URL. It's gross and it's a hack. It's a Band-Aid on Cancer.

They then started looking at the source for ASP.NET proper and then decided to disassemble the code that was running on the other person's machine. They then started to assert their assumptions.

Is the code running what's on disk? For a compiled language, do the binaries reflect the source?

They looked in Temporary ASP.NET files at the compiled ASPX markup pages and found this.

//ExternalSource("D:\WebApplications\Foo\login.aspx",27)
__ctrl.Method = "post";

//ExternalSource("D:\WebApplications\Foo\login.aspx",27)
__ctrl.Action = "login.aspx";

What? Why is someone setting the FORM Action manually? And there's a line number.

They had diff compared all the source code but not the markup/views/html.

Their markup:

<form id="Form1" method="post" runat="server">

Other person's markup:

<form id="Form1" method="post" runat="server" action="Login.aspx">

The other person had hard-coded the action in their source markup. They'd been diffing everything but the markup.

When you are comparing two code-bases, make sure to compare everything or you might just lose a day or two like this person.

Thanks to my reader for sharing this and letting me in on this debugging adventure.

Related Links:


Sponsor: Big thanks to Red Gate for sponsoring the blog feed this week. Check out the Free Starter Edition of their release management tool! Deploy your SQL Server databases, .NET apps and services in a single, repeatable process with Red Gate’s Deployment Manager. Get started now with the free Starter Edition.

About Scott

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

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Checklist: What NOT to do in ASP.NET

February 7, '14 Comments [22] Posted in ASP.NET
Sponsored By

Damian Edwards at NDC 2013 talking about ASP.NETAbout a year ago we thought it would be a good idea to do a talk on "What not to do in ASP.NET?" - basically an anti-patterns talks. We kept seeing folks falling into the same traps and wanted to be prescriptive as there's aspects to ASP.NET that are 10 years old and don't apply to today's internet, but there are also new aspects to ASP.NET that are only a year old, and perhaps haven't soaked into the zeitgeist quite yet.

Damian Edwards gave his version of this talk at NDC 2013 and you can watch the video here if you like, it's very entertaining.

We took the information we gathered from people like Damian, Levi Broderick and others, and Tom FitzMacken put together a whitepaper on the topic. It's not complete, but it covers some of the most common "gotchas" folks run into.

Here are the areas we call out in the whitepaper so far, with highlights below from me.

I hope this helps someone out!


Sponsor: Big Thanks to Aspose for sponsoring the blog this week! Aspose.Total for .NET has all the APIs you need to create, manipulate and convert Microsoft Office documents and a host of other file formats in your applications. Curious? Start a free trial today.

About Scott

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

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Building Modern Web Apps with ASP.NET - A new day of free ASP.NET Training for 2014

February 5, '14 Comments [30] Posted in ASP.NET | Azure
Sponsored By
Scott Hunter and Scott Hanselman talking about What's New in VS2013

Last year, about this time, a bunch of us sat down in a studio to give a full day of tutorials and discussion on "Building Web Apps with ASP.NET." All those videos are online and have lots of good content like:

We headed over to the Microsoft Virtual Academy Studios again just this last week for another full day of discussion, training, as well as a glimpse into the possible future of .NET. Between these two days of videos you'll get a real sense of what's possible and real advice on how to build your next web application.

Today we've got 7 all-new segments for you, each recorded live at the MS Studios.

These videos are featuring folks like Scott Hunter, Levi Broderick, Rowan Miller, Pranav Rastogi, Mads Kristensen, and Louis DeJardin. No marketing folks, just actual developers that work on ASP.NET every day.

ScottHu and ScottHa talking about VS20131: What's New in Visual Studio 2013 for Web Developers - Learn about the latest features in Visual Studio 2013, including dozens of tips and tricks.

image2: Upgrading Applications - Get a deep dive on how to upgrade your older applications to ASP.NET 4.5 and later.

image3: ASP.NET Identity - Explore the new ASP.NET Identity system. Learn how to migrate your existing membership data to the new Identity system and how to integrate with other membership systems.

image4: Web Essentials and the Client Side - Discover how to build modern client-side applications, more simply and quickly, with a host of new features, tips, and tricks in Web Essentials for Visual Studio.

image5: Entity Framework - Have you been using Entity Framework for data access in your web app? In this advanced demo-heavy session, learn the latest features of Entity Framework 6 and get sneak previews of what's coming in version 6.1.

image6: The "Katana" Project - Hear the latest on "Project Katana," the Microsoft implementation of Open Web Interface for .NET. It's a glimpse of the future for cloud-optimizing your ASP.NET applications.

image7: ASP.NET "Project Helios" - Discover "Project Helios," a prototype representing the re-thinking of the core of ASP.NET. Take a look at the future of web development, with a modular, lightweight OWIN host that runs on Internet Information Services (IIS).

Also be sure to explore the new series "Get Started with Windows Azure today" featuring content from ScottGu himself for a full 90 minutes!

image

I hope you have as much fun watching them as we did filming them.


Sponsor: Big Thanks to Aspose for sponsoring the blog this week! Aspose.Total for .NET has all the APIs you need to create, manipulate and convert Microsoft Office documents and a host of other file formats in your applications. Curious? Start a free trial today.

About Scott

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

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Page 1 of 162 in the ASP.NET category Next Page

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