Wednesday, 6 June 2012
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]
aspnet, webmatrix
|
link
|
Saturday, 19 May 2012
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]
aspnet, webmatrix
|
link
|
Tuesday, 8 May 2012
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]
aspnet, webmatrix
|
link
|
Wednesday, 25 April 2012
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]
aspnet, webmatrix
|
link
|
Thursday, 19 April 2012
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]
aspnet, webmatrix
|
link
|
Wednesday, 11 April 2012
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.
[categories]
aspnet, webmatrix
|
link
|
Wednesday, 14 March 2012
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]
aspnet, webmatrix
|
link
|
Wednesday, 7 March 2012
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]
aspnet, webmatrix
|
link
|
Thursday, 1 March 2012
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]
aspnet, webmatrix
|
link
|
Saturday, 25 February 2012
09:41 PM
As noted previously, to display validation errors in ASP.NET Web Pages v2, you can use Html.ValidationMessage and Html.ValidationSummary . You can use one or the other or both. If you use both, one idea is to use ValidationMessage to show an "error icon" and ValidationSummary to show the details: Rather a plain display. However, you can style the output of the messages using some reserved CSS class names, to wit:input-validation-error
field-validation-error
validation-summary-errors
field-validation-valid
input-validation-valid
validation-summary-valid For example, to make a particularly forceful (if ugly) error display, define classes like this:
<style> .validation-summary-errors{ border:2px solid red; color:red; font-weight:bold; padding:10px; margin:10px; width:30%} .field-validation-error{ color:red; font-weight:bold; background-color:yellow;} .input-validation-error{ color:red; font-weight:bold; background-color:pink;} </style> And when the errors appear, they will be noticeable:
Formatting Client-Side Validation ErrorsFormatting errors that are produced by client-side validation requires a little extra work. If you're using only server-side validation, you can just define the CSS classes and everything will work, albeit only after a postback.
If you're using client-side validation, in addition to registering the .js libraries and adding calls to Validation.GetHtml Validation.For (changed in the Feb 2012 Beta of v2), you also need to add a call to Validation.ClassFor to the element being validated, like this:
<input type="text" name="coursename" value=@Request.Form["coursename"] @Validation.For("coursename") @Validation.ClassFor("coursename") /> @Html.ValidationMessage("coursename", "<=") Validation.ClassFor is similar to Validation.GetHtml Validation.For in that it emits attributes that jQuery can hook. In this case, it lets jQuery dynamically add the name of an appropriate class (e.g. field-validation-error ) if there's an error condition.
Client-side validation can also actually use the style that you define for input-validation-error . Server-only validation won't emit a class attribute into the <input> element that references this CSS class. However, client-side validation does all this work dynamically, and anything you define in input-validation-error will indeed be reflected in an <input> element where there's an error:
Static and Dynamic Error DisplayNotice that there are two classes for each display: one is for the error condition (e.g. validation-summary-errors ) and one is for the default/normal/non-error condition (e.g. validation-summary-valid ). This points out something that's important to understand about the validation-error display methods: they always render markup. This might not be apparent if you use the methods only to display validation errors, because if there's no error, no error text is rendered and you don't see anything. Even in that case, tho, the surrounding markup (a <div> or <span> ) is rendered. You can see this if look you at the page source when there's no error.
Always rendering the markup for error display has a downside. For example, if you display a static message using Html.ValidationSummary , that static message is always displayed, even if there's no error.
You can fix this in a couple of ways. One is to put the Html.ValidationSummary call into a conditional block so it's only rendered under error conditions:
@if(IsPost && !Validation.IsValid()){ <text>@Html.ValidationSummary("Please fix the marked errors") </text> } Another way is to control the display via CSS. An easy way to do that is to set the display property in the CSS class for the –valid classes to (e.g.) none .
One reason to always render the error display markup is to allow you to define a layout for the page that doesn’t jump around depending on whether an error is being displayed or not. (For the same reason, the validation controls in ASP.NET Web Forms have a Display property that can be set to None , Static , or Dynamic .) For example, you can use the CSS classes to define a <div> or <span> element that has a fixed size. The element then always renders the same whether it has an error message in it or not, and the overall page layout remains the same. (Trying to come up with a fixed size for the summary if there's potentially many errors is left as an exercise for the student.)
Anyway, between the two types of display methods and the reserved CSS classes and the ability to conditionally output messages using code, you should be able to come up with almost any kind of validation error display you need. Or so we hope. :-)
See Also
[categories]
aspnet, webmatrix
|
link
|