About

I'm Mike Pope. I live in the Seattle area. I've been a technical writer and editor for over 35 years. I'm interested in software, language, music, movies, books, motorcycles, travel, and ... well, lots of stuff.

Read more ...

Blog Search


(Supports AND)

Feed

Subscribe to the RSS feed for this blog.

See this post for info on full versus truncated feeds.

Quote

Ruling, of course, must be distinguished from governing, which is a more nuanced process that entails give-and-take and the kind of compromises that are often necessary to find a consensus and solutions that will best serve the interests of all Americans.

John Dean



Navigation





<December 2024>
SMTWTFS
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

Categories

  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  
  RSS  

Contact Me

Email me

Blog Statistics

Dates
First entry - 6/27/2003
Most recent entry - 9/4/2024

Totals
Posts - 2655
Comments - 2677
Hits - 2,715,632

Averages
Entries/day - 0.34
Comments/entry - 1.01
Hits/day - 347

Updated every 30 minutes. Last: 3:50 PM Pacific


  08:56 AM

The ASP.NET Web Pages/Razor WebGrid helper lets you enable paging just by setting the rowsPerPage property:
var grid = new WebGrid(source: selectedData, rowsPerPage: 3);

Sometimes you want to be able to both to page and to filter the items that are displayed by the WebGrid helper. Here’s an example from the tutorial I posted on the http://asp.net/web-pages site a couple of weeks ago:


The typical (?) approach here is to create a <form> element and set its method attribute to post:

<form method="post">
<div>
<label for="searchGenre">Genre to look for:</label>
<input type="text" name="searchGenre" value="" />
<input type="Submit" value="Search Genre" /><br/>
(Leave blank to list all movies.)<br/>
</div>
</form>

In the page’s code, you create an is(IsPost) block in which you set up a parameterized SQL query and run that:
@{
var db = Database.Open("WebPagesMovies") ;
var selectCommand = "SELECT * FROM Movies";
var searchTerm = "";

if(IsPost && !Request.QueryString["searchGenre"].IsEmpty() ) {
selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
searchTerm = Request.QueryString["searchGenre"];
}

var selectedData = db.Query(selectCommand, searchTerm);
var grid = new WebGrid(source: selectedData, rowsPerPage:3);
}

This works great—for the first page. But if you use the page navigation to go to the second page, the grid forgets all about your filter.

The problem is that paging in the grid is implemented as a query string. Note that if you go to page 2, the URL changes to something like this:

http://localhost:45661/Movies?page=2

This means that the URL of the page has changed, which means that the browser is doing a GET. Doing a GET means that your posted form data is ignored.

The fix (a fix) is relatively simple: instead of using the POST method for the form, use a GET method:

<form method="get">
<!-- etc -->
</form>

This change causes the form to post the form values in the URL:

http://localhost:45661/Movies.cshtml?searchGenre=Drama&page=2

Then in your page logic, you forget about the IsPost test and just do this:

if(!Request.QueryString["searchGenre"].IsEmpty() ) { // etc }

With this change, your filter form and the WebGrid helper’s paging logic work together quite nicely.

[categories]   ,

[2] |


  09:33 PM

I'm just recording this for now, possibly for later investigation. In ASP.NET Web Pages 2 (Razor), you can take advantage of conditional attributes to set or clear attributes like selected and checked. These attributes don't need a value, they just need to exist, like this:

<select>
<option value="1">One</option>
<option value="2" selected >Two</option>
<option value="3">Three</option>
</select>
So how do you "remember" a list selection after a form submit? Here's one way. This seems a bit kludgy, but I can't offhand think of a way to do this without using JavaScript or something.

< select name="NumberList">
<option
selected=@(Request.Form["NumberList"] == "1")
value="1">One</option>
<option
selected=@(Request.Form["NumberList"] == "2")
value="2">Two</option>
<option
selected=@(Request.Form["NumberList"] == "3")
value="3">Three</option>
</select>

[categories]   ,

|


  11:20 PM

When you use membership security in ASP.NET Web Pages Razor, you can limit access to pages so that only logged-in users can see those pages. One way to do that, as explained before, is to add a test like the following to the top of a page (or to the _PageStart.cshtml page in the protected folder):

@{
    if (!WebSecurity.IsAuthenticated) {
        Response.Redirect("~/Login");
    }
}

If the user isn't logged in, they're redirected to the Login page (in this case, in the site's root).

Nice, but then the user still has to find their way back to the page they originally wanted. Ideally, after the user has logged in, you send them back to the original page automatically. And because they're now logged in, the gatekeeper code lets them through.

The usual way to approach this is to include a return URL address when you redirect to the login page. You can add it as a query-string value to the login URL. Then in the login page, once the user has logged in ok, you can get that return URL and jump back.

Here's an updated version of the code from above with some logic to create a return URL:

if (!WebSecurity.IsAuthenticated) {
     Response.Redirect("~/Login?returnUrl=" + Request.Url.LocalPath);
}

For a URL like the following:

http://localhost/members/info.cshtml

The Request.Url.LocalPath property returns this:

/members/info.cshtml

So the URL of the redirect to the Login page looks like this:

http://localhost/Login?returnUrl=/members/info.cshtml

The login page can then return to the original page using logic like this:

if(IsPost){
    var username = Request.Form["username"];
    var password = Request.Form["password"];
    bool rememberMe = Request.Form["rememberMe"].AsBool();

    // Various kinds of validations and checks for user 
    // existence here, then ...

    if (WebSecurity.Login(username, password, rememberMe)) {
        if(!Request.QueryString["returnUrl"].IsEmpty()){
            Context.RedirectLocal(Request.QueryString["returnUrl"]);
        }
        else{
            Response.Redirect("~/"); // Goes to site home
        }
    }
}

Update (2018 Jul 29)  Someone has noted in the comments that in ASP.NET MVC (.NET Core 2.0+), RedirectLocal is a method on the base page, not on HttpContext, as it was when I wrote this. See the docs.

Notice that the redirection back to the original page is done with the Context.RedirectLocal method. You could use Response.Redirect. But Context.RedirectLocal makes sure that redirection is performed only if the return URL is local to the site. This helps prevent someone from hacking in a complete URL that would redirect to an external site in order to try to snatch authentication tokens or what have you.

If you happen to be using the Starter Site template in WebMatrix (v2 Beta or later), the login page already has this built in. In any page that you create where you want to let the user log in and then return, make sure you add a query string when you invoke the login page, and set returnUrl to the local path of the current page, as illustrated above.

[categories]   ,

[4] |


  05:20 PM

In ASP.NET Web Pages/Razor, you use the @ character inside markup to mean "here be code." Like this:

<p>@DateTime.Now</p>

But suppose you want to display the @ character instead of use it to mark code? Like this:

<p>You use the @ character to mark inline code.</p>

Try that in a .cshtml page and you're rewarded with a YSOD:


(Click to embiggen)

Simple fix: escape the @ character with ... another @ character. Like this:

<p>You use the @@ character to mark inline code.</p>

This makes the parser happy.

(h/t, as usual with parser questions: Andrew Nurse)

[categories]   ,

|


  01:04 AM

I was playing around with layout pages in ASP.NET Web Pages the other day and realized that there are actually two ways to pass data from the content page to the layout page: the Page object and the PageData object. You can do either of these in the content page:
Page.Title = "My Page";

PageData["Title"] = "My Page";
And then use either of these in the layout page:
<title>@Page.Title</title>

<title>@PageData["Title"]</title>
So what's the difference? Here's what I got from my usual sources.

First, they really are the same object, just with differences in accessors. To show this, you could do the following:
Page.Me = "Mike";
and then get that value doing this:
<p>@PageData["me"]</p>
Notice that the property/value names — Me, "me" — aren't even case sensitive.

Page is a dynamic object, meaning that the properties aren't fixed. You can make up your own properties for the object, like Page.Title, Page.MyValue, or Page.MyDogIsADoofus, and assign values to them.

Some folks consider syntax like Page.Title to be cleaner than using something like PageData["Title"]. However, this isn't really the same as normal property syntax (e.g., Request.Forms), because the dynamic property isn't getting compile-time type checking. And when you're using a dynamic property, some operations that look like they should work don't, like this:
Page.MyCount = "3";
if(Page.MyCount.IsInt()){
// Fail with compiler error.
}

PageData["name"] is a normal dictionary of name/value pairs. This makes it easier to do two things: a) set the name of the value to pass at run time, and b) use names that would be illegal as property names, like a name that has a space in it.

A shorthand way to understand the difference is that it's essentially the same difference as between ViewBag and ViewData in MVC. Except that (as near as I can tell) you don't need to cast PageData as you see with ViewData in the MVC examples. At least, I haven't had to yet.

If you happen to be running Visual Basic, Page can have issues because Visual Basic has issues with dynamic objects unless you're running in full trust, which you don't in web apps. In that case, use PageData.

[categories]   ,

|


  07:15 AM

Two tips for Razor syntax: using a conditional attribute to set the selected attribute in a list item; working around a syntax restriction on x@x (e.g. <h@level>).

Tip 1
When I wrote recently about conditional attributes in ASP.NET Web Pages version 2 (new for that version), the example was the checked attribute of a checkbox. What about dynamically selecting an item in a <select> list?

I wanted the <select> list to remember the user’s choice after the page had been submitted. In an <input> element, you can do this by setting the value attribute to the appropriate item in the Request collection:

<input type="input" name="firstName" value="@Request.Form["firstName"]" />

In my case, I'm populating the <select> list from a database query (i.e. a collection) using a foreach loop. So I can use this code to compare each item against the user's most recent selection and set the selected attribute conditionally:

<select name="selectGenre">
@foreach(var row in db.Query("Select DISTINCT Genre FROM Movies ORDER BY Genre")){
<option
selected=@(row.Genre==Request.Form["selectGenre"])>
@row.Genre
</option>
}
</select>
As each row is processed, I compare its Genre property/field against whatever was selected for the last page submission. If there's a match, the comparison returns true and the selected attribute is rendered.

(In the actual app, I'm sticking the results of the query into a variable that I in turn cache, so it's not quite as ineffecient as running a query every time the page runs. :-) )

Tip 2
This one was raised as a question by MVP Kris van der Mast, and might already be noted elsewhere. Kris wanted to set a heading level dynamically (e.g., <h1>, <h2>, etc.) which he tried by using the syntax <h@level>, where level is a variable.[1] In particular, he was using this syntax with success in Web Pages v1 (or possibly in a preview version of v1), but it definitely wasn't working with Web Pages v2.

It turns out Kris's original syntax was not supposed to work, and this had been fixed up for v2. The issue is that any string of the form x@x (e.g., <h@level>) is supposed to be interpreted as an email address. (In the words of one of the developers, this detection is "admittedly basic.") For a situation like Kris's, the syntax that works is x@(x) — for example, <h(@level)>. The parentheses foil the email-address detection and are otherwise of course benign.

[1] Maybe he didn't want to do this thing specifically, but it was a good example for this purpose.

[categories]   ,

|


  03:17 PM

Just a note that we just posted a whitepaper [PDF] written by the super-smart Stefan Schakow that discusses various ways in which ASP.NET 4 lets you extend security. For example, the paper describes:
  • How to specify various encryption algorithms for the <machineKey> section, including a custom class. (IOW, you can specify custom encryption for cookies, viewstate, etc.) There's now an API to manage custom encryption programmatically as well.

  • How to share forms authentication tickets between ASP.NET 2.0 and ASP.NET 4.

  • How to customize the security checks that are performed on incoming URLs. The security checks have been made pluggable so that you can use custom code for tasks like checking the length of the URL, checking for invalid or dangerous characters in the URL (for example, you can allows some characters that would be rejected by default), and matching portions of the URL to physical paths.

  • How request validation has changed for ASP.NET 4 and how to create a custom request-validation check.

  • How to use custom classes to encode HTML markup, headers, and URLs.

[categories]  

|


  10:52 PM

Ok, we've now got a list of the new features for ASP.NET Web Pages v2 posted. Here's a list of the stuff that's been added since the December 2011 preview, as recounted in the updated Top Features doc:
  • Conditional attributes. Described earlier.

  • Validation.GetHtml has been changed to Validation.For. This method renders the goo that's used to hook client-side validation. See earlier.

  • The ~ operator is now recognized in HTML markup, so you don't need to use Href any more to resolve it. See earlier. I think this is my favorite feature, actually, even tho some might consider it comparatively small, dunno.

  • The Scripts helper was renamed to Assets (aka the Assets Manager), and the method names were tweaked. If you were using Scripts to register scripts or .css files, you have to change those references.
The rest of the doc describes the features that are new in v2 generally, starting in the December preview. If you installed that preview, then you'll know all about those. :-)

[categories]   ,

|


  08:40 AM

Another improvement in the Beta release of ASP.NET Web Pages v2 is better integration of the ~ operator. The ~ operator, as ASP.NET people know, resolves to the root path of the current website. (See also) This is handy for creating URL paths, because it means that a) you don't need to hard-code an absolute URL and b) it isn't relative to the location of the current page. You don't need to worry about how many folders up you need to count (e.g. ../../path) or about what happens if the current page moves.

In Web Pages v1 (as in all ASP.NET code), the ~ operator is supported. But it only works in server code; it's not supported in native HTML, so to speak. If you want to create a path that uses the ~ operator, you therefore have to wrap the path into something that tells ASP.NET that you've got server code. Inside of markup, we use the Href method. For example, to create an <a> link that incorporates it, you have to do this:

<a href="@Href("~/Default")">Home</a>

It works, but it's not very intuitive.

For v2 Beta, the .cshtml parser was enhanced so that it can recognize the ~ operator inline with normal markup. So the previous example can now be written like this:

<a href="~/Default">Home</a>

No need for the Href method any more. In .cshtml pages (not plain .html or .htm pages, of course), you really can treat the ~ operator as, in effect, pass-through HTML. I was working on something the other day that used a ~ path with inline server code, and it came out like this:

@grid.GetHtml(
columns: grid.Columns(
grid.Column(format: @<a href="~/EditMovie?id=@item.ID">Edit</a>),
grid.Column("Title"),
grid.Column("Year")
)
)
Slick. Every time now that I work with non-ASP.NET pages I wish I could use it there, too. :-)

[categories]   ,

|


  02:07 PM

The beta release of ASP.NET Web Pages has been released (for example, as part of the ASP.NET MVC 4 Beta release). There are only a few differences from the December 2011 Developer Preview release. (Details when we've got them posted.)

A very cool feature is what's being called conditional attributes. The idea is that in markup, you can set the value of an element's attribute to a variable or expression. If the variable or expression returns false or null, the entire attribute is not rendered. There are a variety of uses for this, but it does a great job of solving a problem we inherited from HTML 1.0 (I think it was).

The problem manifests in cases where the simple appearance of an attribute name — regardless of its value — is sufficient to trigger HTML behavior. One case is the checkbox, i.e., the <input type="checkbox"> element:

<input type="checkbox" name="check1" value="check1" checked />
<input type="checkbox" name="check1" value="check1" checked="true" />
<input type="checkbox" name="check1" value="check1" checked="anyThingAtAll" />

Notice that in the first one, the checked attribute doesn't even have a value. Nonetheless, all of these work the same, namely they produce checkbox that's selected.

There's a similar situation with items in a selection list:

<select>
<option value="A">A</option>
<option value="B" selected>B</option>
<option value="C">C</option>
</select>

Technically, to select item B, the tag should read <option value="B" selected="selected">, but just including the selected attribute works.

All of this presents a problem when you want to use code in ASP.NET Web Pages to check a checkbox or select a list item. To just set a normal attribute value, you can use inline Razor code like this:

<input type="text" name="text1" value="@Request.Form["text1"]" />

But that doesn't work for the checked or selected attributes, since it doesn't matter what you set those attributes to.

The solution up to now has been to use more elaborate code in the page to render (or not render) the entire attribute, not just its value. Here's one example:

<input type="checkbox" name="check1" value="check1"
@if(Request.QueryString["val1"] == "true"){
<text>checked="checked"</text>
}
/>

Or if you were inclined, you could use the C# ternary operator, like this:

<input type="checkbox" name="check1" value="check1" 
@(Request.QueryString["val1"] == "true" ? Html.Raw("checked=\"checked\"") : null)
/>

Anyway, both of these methods were a little clunky.

It's now way simpler. As I say, you can now set an attribute to a code value, and if the value is true or non-null, the attribute renders. If it's false or null, the attribute doesn't render. Here's an example:

@{
bool check1 = false;
if(Request.QueryString["val1"] == "true"){
check1=true;
}
}

Then in the markup:

<input type="checkbox" name="check1" value="check1" checked="@check1" />

Magic.

You have to be careful that you don't assume that this works for all "truthy" and "falsy" values. For example, an empty string is not a false, so you can't return "" in order to kill the attribute. You could do something like this:

<input type="checkbox" name="check1" value="check1" 
checked=@(!(Request.QueryString["val1"].IsEmpty())) />

but this will render the attribute no matter what actual value ("true", "false", "foo") happens to be in the query string for val1.

Here's a page where you can see in a little more detail how this works. Pass query-string values like ?val1=true or ?val2=true to see what happens.

@{
bool checked1=false;
bool checked2=false;
Object someObject = null;
string aString = String.Empty;

if(Request.QueryString["val1"] == "true"){
checked1=true;
}

if(Request.QueryString["val2"] == "true"){
checked2=true;
someObject = this;
aString="Hello, conditional attributes!";
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Test Conditional Attributes</title>
</head>
<body>

<form method="post">

<div>
<input type="checkbox" name="check1" value="check1" checked="@checked1" />
Value 1
<br/>
<input type="checkbox" name="check2" value="check2" checked="@checked2" />
Value 2
<br/>
<input type="checkbox" name="check3" value="check3" checked="@someObject" />
Some object
<br/>
<input type="checkbox" name="check4" value="check4" checked="@Request.Form["name"]" />
Request.Form["name"]
<br/>
<input type="checkbox" name="check5" value="check5" checked="@aString" />
aString
</div>

<div>
<input type="submit" name="buttonSubmit" value="Submit" />
</div>
</form>
</body>
</html>

[categories]   ,

|