Posted on March 13, 2008 09:00 by mcollins

At the end of my previous post, I had built a custom ASP.NET server control that implemented a custom layout template for the ASP.NET Login control.  I had also presented this solution as saying that now anyone creating a theme could create and replace the template for a Login or similar ASP.NET control.  That much is true.  There were two questions that I really didn't answer:

  • What if you want to replace the default Login template for all themes?
  • How do I get rid of the enclosing table element that the Login control generates?

 

As I discussed in the previous post, one solution to the problem is to create a custom LayoutTemplate for the Login control in the host page.  Then the template will apply to the page and all ASP.NET themes.  However, this doesn't solve the second issue because we still have a containing TABLE element around our Login control.

To solve the first and second problem together, we need to completely rewrite the rendering logic for the Login control.  We can do this thanks to an extensibility mechanism built into ASP.NET: adapters.

Using adapters, you can instruct ASP.NET controls to call an external component to perform custom rendering of the control instead of using the default rendering.  In the case of the Login control, we can tell the control to call our LoginAdapter control and completely redo the rendering logic for the Login control while still getting the benefits mentioned previously of using the Login control and the Login control's event handling logic.  To the page developer, there's no difference because the Login control continues to work the same.  To the user and the browser, the Login control can look completely different.

The trick to using adapters is that you have to register them with ASP.NET.  This is done through the creation of a custom .browser file in the App_Browsers directory of the ASP.NET application.  Using the sample code from the ASP.NET 2.0 CSS Adapters sample, I created the following .browser file:

   1: <browsers>
   2:     <browser refID="Default">
   3:         <controlAdapters>
   4:             <adapter controlType="System.Web.UI.WebControls.Login"
   5:                      adapterType="LoginAdapter"/>
   6:         </controlAdapters>
   7:     </browser>
   8:     <browser id="W3C_Validator" parentID="default">
   9:         <identification>
  10:             <userAgent match="^W3C_Validator"/>
  11:         </identification>
  12:         <capabilities>
  13:             <capability name="browser" value="W3C Validator"/>
  14:             <capability name="ecmaScriptVersion" value="1.2"/>
  15:             <capability name="javascript" value="true"/>
  16:             <capability name="supportsCss" value="true"/>
  17:             <capability name="supportsCallback" value="true"/>
  18:             <capability name="tables" value="true"/>
  19:             <capability name="tagWriter" 
  20:                 value="System.Web.UI.HtmlTextWriter"/>
  21:             <capability name="w3cdomversion" value="1.0"/>
  22:         </capabilities>
  23:     </browser>
  24: </browsers>

 

This .browser file tells ASP.NET that when the System.Web.UI.WebControls.Login control is being used on a page, that ASP.NET should attach my LoginAdapter adapter to the Login control and use my adapter to perform the control rendering.

Using my custom template from the previous post as the starting point, I modified my template and created the following adapter code:

   1: using System;
   2: using System.Diagnostics;
   3: using System.Diagnostics.CodeAnalysis;
   4: using System.Web.UI;
   5: using System.Web.UI.HtmlControls;
   6: using System.Web.UI.WebControls;
   7: using System.Web.UI.WebControls.Adapters;
   8:  
   9: /// <summary>
  10: /// Implements a custom control adapter for the ASP.NET <see cref="Login"/>
  11: /// control that produces CSS-friendly HTML.
  12: /// </summary>
  13: public class LoginAdapter : WebControlAdapter {
  14:     private Control container;
  15:  
  16:     /// <summary>
  17:     /// Creates the child controls that will be used to render the
  18:     /// contents of the <see cref="Login"/> control.
  19:     /// </summary>
  20:     /// <param name="e">The event arguments.</param>
  21:     protected override void OnInit(EventArgs e) {
  22:         base.OnInit(e);
  23:  
  24:         Login login = Control as Login;
  25:         Debug.Assert(login != null, "login != null");
  26:  
  27:         login.LoginError += login_LoginError;
  28:  
  29:         ITemplate layoutTemplate = login.LayoutTemplate;
  30:         if (layoutTemplate == null) {
  31:             layoutTemplate = new DefaultLoginTemplate(login);
  32:         }
  33:  
  34:         container = new Control();
  35:         layoutTemplate.InstantiateIn(container);
  36:         login.Controls.Clear();
  37:         login.Controls.Add(container);
  38:     }
  39:  
  40:     /// <summary>
  41:     /// Sets the text and check box settings for the user name, password,
  42:     /// and remember me fields on the login form.
  43:     /// </summary>
  44:     /// <param name="e">The event arguments.</param>
  45:     protected override void OnLoad(EventArgs e) {
  46:         base.OnLoad(e);
  47:  
  48:         if (!Page.IsPostBack) {
  49:             Login login = Control as Login;
  50:             Debug.Assert(login != null, "login != null");
  51:  
  52:             ITextControl userName = container.FindControl("UserName") as
  53:                 ITextControl;
  54:             Debug.Assert(userName != null, "userName != null");
  55:             userName.Text = login.UserName;
  56:  
  57:             ITextControl password = container.FindControl("Password") as
  58:                 ITextControl;
  59:             Debug.Assert(password != null, "password != null");
  60:             password.Text = login.Password;
  61:  
  62:             ICheckBoxControl rememberMe = container.FindControl("RememberMe")
  63:                 as ICheckBoxControl;
  64:             Debug.Assert(rememberMe != null, "rememberMe != null");
  65:             rememberMe.Checked = login.RememberMeSet;
  66:         }
  67:     }
  68:  
  69:     /// <summary>
  70:     /// Detaches the adapter from the <see cref="Login.LoginError"/> event.
  71:     /// </summary>
  72:     /// <param name="e">The event arguments.</param>
  73:     protected override void OnUnload(EventArgs e) {
  74:         Login login = Control as Login;
  75:         Debug.Assert(login != null, "login != null");
  76:         login.LoginError -= login_LoginError;
  77:  
  78:         base.OnUnload(e);
  79:     }
  80:  
  81:     /// <summary>
  82:     /// Renders the opening tag that encapsulates the <see cref="Login"/>
  83:     /// control's content.
  84:     /// </summary>
  85:     /// <param name="writer">
  86:     /// The <see cref="HtmlTextWriter"/> object to use to output the
  87:     /// opening tag for the control.
  88:     /// </param>
  89:     protected override void RenderBeginTag(HtmlTextWriter writer) {
  90:         Login login = Control as Login;
  91:         Debug.Assert(login != null, "login != null");
  92:  
  93:         writer.AddAttribute(HtmlTextWriterAttribute.Id, login.ClientID);
  94:  
  95:         if (!login.ControlStyle.IsEmpty) {
  96:             if (!String.IsNullOrEmpty(login.ControlStyle.CssClass)) {
  97:                 writer.AddAttribute(HtmlTextWriterAttribute.Class,
  98:                     login.ControlStyle.CssClass);
  99:             }
 100:  
 101:             foreach (string key in login.Style.Keys) {
 102:                 writer.AddStyleAttribute(key, login.Style[key]);
 103:             }
 104:         }
 105:  
 106:         writer.RenderBeginTag(HtmlTextWriterTag.Div);
 107:     }
 108:  
 109:     /// <summary>
 110:     /// Renders the inner contents of the <see cref="Login"/> control.
 111:     /// </summary>
 112:     /// <param name="writer">
 113:     /// The <see cref="HtmlTextWriter"/> object to use to output the
 114:     /// control's contents.
 115:     /// </param>
 116:     protected override void RenderContents(HtmlTextWriter writer) {
 117:         container.RenderControl(writer);
 118:     }
 119:  
 120:     /// <summary>
 121:     /// Renders the closing outer tag for the <see cref="Login"/> control.
 122:     /// </summary>
 123:     /// <param name="writer">
 124:     /// The <see cref="HtmlTextWriter"/> object to use to output the
 125:     /// control's contents.
 126:     /// </param>
 127:     protected override void RenderEndTag(HtmlTextWriter writer) {
 128:         writer.RenderEndTag();
 129:     }
 130:  
 131:     /// <summary>
 132:     /// Displays the failure text in the <see cref="Login"/> control's
 133:     /// output if a login error occurred.
 134:     /// </summary>
 135:     /// <param name="sender">The <see cref="Login"/> control.</param>
 136:     /// <param name="e">The event arguments.</param>
 137:     private void login_LoginError(object sender, EventArgs e) {
 138:         Login login = Control as Login;
 139:         Debug.Assert(login != null, "login != null");
 140:  
 141:         ITextControl failureText = container.FindControl("FailureText") as
 142:             ITextControl;
 143:         Debug.Assert(failureText != null, "failureText != null");
 144:         failureText.Text = login.FailureText;
 145:  
 146:         Control parent = ((Control)failureText).Parent;
 147:         if (!parent.Visible) {
 148:             parent.Visible = true;
 149:         }
 150:     }
 151:  
 152:     /// <summary>
 153:     /// Implements the default template for the <see cref="Login"/> control.
 154:     /// </summary>
 155:     private class DefaultLoginTemplate : ITemplate {
 156:         /// <summary>
 157:         /// The <see cref="Login"/> control that the template is a template
 158:         /// for.
 159:         /// </summary>
 160:         private Login login;
 161:  
 162:         /// <summary>
 163:         /// Constructs a new <see cref="DefaultLoginTemplate"/> object.
 164:         /// </summary>
 165:         /// <param name="login">
 166:         /// The <see cref="Login"/> control that the template is a template
 167:         /// for.
 168:         /// </param>
 169:         public DefaultLoginTemplate(Login login) {
 170:             this.login = login;
 171:         }
 172:  
 173:         /// <summary>
 174:         /// Adds the standard <see cref="Login"/> controls to the template
 175:         /// container.
 176:         /// </summary>
 177:         /// <param name="container">
 178:         /// The parent container that the controls will be added to as
 179:         /// children.
 180:         /// </param>
 181:         public void InstantiateIn(Control container) {
 182:             CreateTitleControl(container);
 183:             CreateInstructionControl(container);
 184:             CreateFailureControl(container);
 185:             CreateUserInformationFieldSet(container);
 186:             CreateOptionsFieldSet(container);
 187:             CreateLoginButton(container);
 188:             CreateLinks(container);
 189:         }
 190:  
 191:         /// <summary>
 192:         /// Creates the controls used to render the link to create a new
 193:         /// user account.
 194:         /// </summary>
 195:         /// <param name="hasCreateUserIconUrl">
 196:         /// True if the icon should be rendered.
 197:         /// </param>
 198:         /// <param name="hasCreateUserText">
 199:         /// True if the link text should be rendered.
 200:         /// </param>
 201:         /// <returns>The link.</returns>
 202:         private Control CreateCreateUserLink(bool hasCreateUserIconUrl, 
 203:             bool hasCreateUserText) {
 204:             var listItem = new HtmlGenericControl("LI") {
 205:                 EnableViewState = false
 206:             };
 207:  
 208:             var link = new HyperLink() {
 209:                 EnableViewState = false,
 210:                 NavigateUrl = login.CreateUserUrl
 211:             };
 212:             link.MergeStyle(login.HyperLinkStyle);
 213:             listItem.Controls.Add(link);
 214:  
 215:             if (hasCreateUserIconUrl) {
 216:                 var image = new Image() {
 217:                     EnableViewState = false,
 218:                     ImageUrl = login.CreateUserIconUrl
 219:                 };
 220:                 link.Controls.Add(image);
 221:             }
 222:             if (hasCreateUserText) {
 223:                 var text = new Literal() {
 224:                     Text = login.CreateUserText
 225:                 };
 226:                 link.Controls.Add(text);
 227:             }
 228:  
 229:             return listItem;
 230:         }
 231:  
 232:         /// <summary>
 233:         /// Creates the controls that are used to render the failure message
 234:         /// for the <see cref="Login"/> control.
 235:         /// </summary>
 236:         /// <param name="container">
 237:         /// The parent container <see cref="Control"/> that the new control
 238:         /// is to be added to.
 239:         /// </param>
 240:         private void CreateFailureControl(Control container) {
 241:             var failureTextDiv = new HtmlGenericControl("DIV") {
 242:                 EnableViewState = false,
 243:                 Visible = false
 244:             };
 245:             SetCssStyles(failureTextDiv, login.FailureTextStyle);
 246:             container.Controls.Add(failureTextDiv);
 247:  
 248:             var failureText = new Literal {
 249:                 EnableViewState = false,
 250:                 ID = "FailureText"
 251:             };
 252:             failureTextDiv.Controls.Add(failureText);
 253:         }
 254:  
 255:         /// <summary>
 256:         /// Creates the controls used to render the link to navigate to a
 257:         /// help page for the login form.
 258:         /// </summary>
 259:         /// <param name="hasCreateUserIconUrl">
 260:         /// True if the icon should be rendered.
 261:         /// </param>
 262:         /// <param name="hasCreateUserText">
 263:         /// True if the link text should be rendered.
 264:         /// </param>
 265:         /// <returns>The link.</returns>
 266:         private Control CreateHelpPageLink(bool hasHelpPageIconUrl, 
 267:             bool hasHelpPageText) {
 268:             var listItem = new HtmlGenericControl("LI") {
 269:                 EnableViewState = false
 270:             };
 271:  
 272:             var link = new HyperLink() {
 273:                 EnableViewState = false,
 274:                 NavigateUrl = login.HelpPageUrl
 275:             };
 276:             link.MergeStyle(login.HyperLinkStyle);
 277:             listItem.Controls.Add(link);
 278:  
 279:             if (hasHelpPageIconUrl) {
 280:                 var image = new Image() {
 281:                     EnableViewState = false,
 282:                     ImageUrl = login.HelpPageIconUrl
 283:                 };
 284:                 link.Controls.Add(image);
 285:             }
 286:             if (hasHelpPageText) {
 287:                 var text = new Literal() {
 288:                     Text = login.HelpPageText
 289:                 };
 290:                 link.Controls.Add(text);
 291:             }
 292:  
 293:             return listItem;
 294:         }
 295:  
 296:         /// <summary>
 297:         /// Creates the controls that are used to render the instructions
 298:         /// for the <see cref="Login"/> control.
 299:         /// </summary>
 300:         /// <param name="container">
 301:         /// The parent container <see cref="Control"/> that the new control
 302:         /// is to be added to.
 303:         /// </param>
 304:         private void CreateInstructionControl(Control container) {
 305:             var instructions = new HtmlGenericControl("DIV") {
 306:                 EnableViewState = false,
 307:                 InnerHtml = login.InstructionText
 308:             };
 309:             SetCssStyles(instructions, login.InstructionTextStyle);
 310:             container.Controls.Add(instructions);
 311:         }
 312:  
 313:         /// <summary>
 314:         /// Creates the controls used to render helpful links to other pages.
 315:         /// </summary>
 316:         /// <param name="container">
 317:         /// The parent container <see cref="Control"/> that the new control
 318:         /// is to be added to.
 319:         /// </param>
 320:         private void CreateLinks(Control container) {
 321:             bool hasCreateUserIconUrl =
 322:                 !String.IsNullOrEmpty(login.CreateUserIconUrl);
 323:             bool hasCreateUserText =
 324:                 !String.IsNullOrEmpty(login.CreateUserText);
 325:             bool hasCreateUserLink = hasCreateUserIconUrl || hasCreateUserText;
 326:             bool hasPasswordRecoveryIconUrl =
 327:                 !String.IsNullOrEmpty(login.PasswordRecoveryIconUrl);
 328:             bool hasPasswordRecoveryText =
 329:                 !String.IsNullOrEmpty(login.PasswordRecoveryText);
 330:             bool hasPasswordRecoveryLink = hasPasswordRecoveryIconUrl ||
 331:                 hasPasswordRecoveryText;
 332:             bool hasHelpPageIconUrl =
 333:                 !String.IsNullOrEmpty(login.HelpPageIconUrl);
 334:             bool hasHelpPageText = !String.IsNullOrEmpty(login.HelpPageText);
 335:             bool hasHelpLink = hasHelpPageIconUrl || hasHelpPageText;
 336:             if (hasCreateUserLink || hasPasswordRecoveryLink || hasHelpLink) {
 337:                 var linkList = new HtmlGenericControl("UL") {
 338:                     EnableViewState = false
 339:                 };
 340:                 container.Controls.Add(linkList);
 341:  
 342:                 if (hasCreateUserLink) {
 343:                     linkList.Controls.Add(CreateCreateUserLink(
 344:                         hasCreateUserIconUrl, hasCreateUserText));
 345:                 }
 346:                 if (hasPasswordRecoveryLink) {
 347:                     linkList.Controls.Add(CreatePasswordRecoveryLink(
 348:                         hasPasswordRecoveryIconUrl, hasPasswordRecoveryText));
 349:                 }
 350:                 if (hasHelpLink) {
 351:                     linkList.Controls.Add(CreateHelpPageLink(
 352:                         hasHelpPageIconUrl, hasHelpPageText));
 353:                 }
 354:             }
 355:         }
 356:  
 357:         /// <summary>
 358:         /// Creates the <see cref="Button"/> control used to initiate the
 359:         /// login process.
 360:         /// </summary>
 361:         /// <param name="container">
 362:         /// The parent container <see cref="Control"/> that the new control
 363:         /// is to be added to.
 364:         /// </param>
 365:         private void CreateLoginButton(Control container) {
 366:             var loginButton = new Button {
 367:                 CommandName = "Login",
 368:                 EnableViewState = false,
 369:                 ID = "LoginButton",
 370:                 Text = login.LoginButtonText,
 371:                 ValidationGroup = login.ID
 372:             };
 373:             container.Controls.Add(loginButton);
 374:         }
 375:  
 376:         /// <summary>
 377:         /// Creates the controls that are used to render the
 378:         /// &lt;FIELDSET&gt; containing authentication options.
 379:         /// </summary>
 380:         /// <param name="container">
 381:         /// The parent container <see cref="Control"/> that the new control
 382:         /// is to be added to.
 383:         /// </param>
 384:         private void CreateOptionsFieldSet(Control container) {
 385:             var rememberMeFieldSet = new HtmlGenericControl("FIELDSET") {
 386:                 EnableViewState = false,
 387:                 ID = "RememberMeFieldSet"
 388:             };
 389:             container.Controls.Add(rememberMeFieldSet);
 390:  
 391:             HtmlGenericControl rememberMeLegend =
 392:                 new HtmlGenericControl("LEGEND") {
 393:                     EnableViewState = false,
 394:                     InnerText = "Options"
 395:                 };
 396:             rememberMeFieldSet.Controls.Add(rememberMeLegend);
 397:  
 398:             var rememberMe = new CheckBox {
 399:                 EnableViewState = false,
 400:                 ID = "RememberMe",
 401:                 Text = login.RememberMeText
 402:             };
 403:             rememberMe.MergeStyle(login.CheckBoxStyle);
 404:             rememberMeFieldSet.Controls.Add(rememberMe);
 405:         }
 406:  
 407:         /// <summary>
 408:         /// Creates the controls used to capture the user's password.
 409:         /// </summary>
 410:         /// <param name="container">
 411:         /// The container <see cref="Control"/> that the password controls
 412:         /// will be added to.
 413:         /// </param>
 414:         private void CreatePasswordControls(Control container) {
 415:             var passwordLabel = new Label {
 416:                 AssociatedControlID = "Password",
 417:                 EnableViewState = false,
 418:                 ID = "PasswordLabel",
 419:                 Text = login.PasswordLabelText
 420:             };
 421:             passwordLabel.MergeStyle(login.LabelStyle);
 422:             container.Controls.Add(passwordLabel);
 423:  
 424:             var password = new TextBox {
 425:                 ID = "Password",
 426:                 TextMode = TextBoxMode.Password
 427:             };
 428:             password.MergeStyle(login.TextBoxStyle);
 429:             container.Controls.Add(password);
 430:  
 431:             var passwordValidator = new RequiredFieldValidator {
 432:                 ControlToValidate = "Password",
 433:                 EnableViewState = false,
 434:                 ID = "PasswordRequired",
 435:                 Text = login.PasswordRequiredErrorMessage,
 436:                 ToolTip = login.PasswordRequiredErrorMessage,
 437:                 ValidationGroup = login.ID
 438:             };
 439:             passwordValidator.MergeStyle(login.ValidatorTextStyle);
 440:             container.Controls.Add(passwordValidator);
 441:         }
 442:  
 443:         /// <summary>
 444:         /// Creates the controls used to render the link to recover a
 445:         /// lost password.
 446:         /// </summary>
 447:         /// <param name="hasCreateUserIconUrl">
 448:         /// True if the icon should be rendered.
 449:         /// </param>
 450:         /// <param name="hasCreateUserText">
 451:         /// True if the link text should be rendered.
 452:         /// </param>
 453:         /// <returns>The link.</returns>
 454:         private Control CreatePasswordRecoveryLink(
 455:             bool hasPasswordRecoveryIconUrl, bool hasPasswordRecoveryText) {
 456:             var listItem = new HtmlGenericControl("LI") {
 457:                 EnableViewState = false
 458:             };
 459:  
 460:             var link = new HyperLink() {
 461:                 EnableViewState = false,
 462:                 NavigateUrl = login.PasswordRecoveryUrl
 463:             };
 464:             link.MergeStyle(login.HyperLinkStyle);
 465:             listItem.Controls.Add(link);
 466:  
 467:             if (hasPasswordRecoveryIconUrl) {
 468:                 var image = new Image() {
 469:                     EnableViewState = false,
 470:                     ImageUrl = login.PasswordRecoveryIconUrl
 471:                 };
 472:                 link.Controls.Add(image);
 473:             }
 474:             if (hasPasswordRecoveryText) {
 475:                 var text = new Literal() {
 476:                     Text = login.PasswordRecoveryText
 477:                 };
 478:                 link.Controls.Add(text);
 479:             }
 480:  
 481:             return listItem;
 482:         }
 483:  
 484:         /// <summary>
 485:         /// Creates the controls that are used to render the title of the
 486:         /// <see cref="Login"/> control.
 487:         /// </summary>
 488:         /// <param name="container">
 489:         /// The parent container <see cref="Control"/> that the new control
 490:         /// is to be added to.
 491:         /// </param>
 492:         private void CreateTitleControl(Control container) {
 493:             var title = new HtmlGenericControl("H1") {
 494:                 EnableViewState = false,
 495:                 InnerText = login.TitleText
 496:             };
 497:             SetCssStyles(title, login.TitleTextStyle);
 498:             container.Controls.Add(title);
 499:         }
 500:  
 501:         /// <summary>
 502:         /// Creates the controls that are used to render the user information
 503:         /// field set for the <see cref="Login"/> control.
 504:         /// </summary>
 505:         /// <param name="container">
 506:         /// The parent container <see cref="Control"/> that the new control
 507:         /// is to be added to.
 508:         /// </param>
 509:         private void CreateUserInformationFieldSet(Control container) {
 510:             var userInformationFieldSet =
 511:                 new HtmlGenericControl("FIELDSET") {
 512:                     EnableViewState = false,
 513:                     ID = "UserInformationFieldSet"
 514:                 };
 515:             container.Controls.Add(userInformationFieldSet);
 516:  
 517:             var userInformationLegend =
 518:                 new HtmlGenericControl("LEGEND") {
 519:                     EnableViewState = false,
 520:                     InnerText = "User account information"
 521:                 };
 522:             userInformationFieldSet.Controls.Add(userInformationLegend);
 523:  
 524:             CreateUserNameControls(userInformationFieldSet);
 525:             CreatePasswordControls(userInformationFieldSet);
 526:         }
 527:  
 528:         /// <summary>
 529:         /// Creates the controls used to capture the user's account name.
 530:         /// </summary>
 531:         /// <param name="container">
 532:         /// The container <see cref="Control"/> that the user name controls
 533:         /// will be added to.
 534:         /// </param>
 535:         private void CreateUserNameControls(Control container) {
 536:             var userNameLabel = new Label {
 537:                 AssociatedControlID = "UserName",
 538:                 EnableViewState = false,
 539:                 ID = "UserNameLabel",
 540:                 Text = login.UserNameLabelText
 541:             };
 542:             userNameLabel.MergeStyle(login.LabelStyle);
 543:             container.Controls.Add(userNameLabel);
 544:  
 545:             var userName = new TextBox {
 546:                 ID = "UserName"
 547:             };
 548:             userName.MergeStyle(login.TextBoxStyle);
 549:             container.Controls.Add(userName);
 550:  
 551:             var userNameValidator = new RequiredFieldValidator {
 552:                 ControlToValidate = "UserName",
 553:                 EnableViewState = false,
 554:                 ID = "UserNameRequired",
 555:                 Text = login.UserNameRequiredErrorMessage,
 556:                 ToolTip = login.UserNameRequiredErrorMessage,
 557:                 ValidationGroup = login.ID
 558:             };
 559:             userNameValidator.MergeStyle(login.ValidatorTextStyle);
 560:             container.Controls.Add(userNameValidator);
 561:         }
 562:  
 563:         /// <summary>
 564:         /// Sets the CSS class name and CSS styles for a
 565:         /// <see cref="HtmlControl"/>.
 566:         /// </summary>
 567:         /// <param name="control">
 568:         /// The <see cref="HtmlControl"/> control.
 569:         /// </param>
 570:         /// <param name="style">
 571:         /// A <see cref="Style"/> object containing the CSS settings for
 572:         /// the control.
 573:         /// </param>
 574:         private void SetCssStyles(HtmlControl control, Style style) {
 575:             if (!style.IsEmpty) {
 576:                 if (!String.IsNullOrEmpty(style.CssClass)) {
 577:                     control.Attributes.Add("class", style.CssClass);
 578:                 }
 579:  
 580:                 var styles = style.GetStyleAttributes(login.Page);
 581:                 foreach (string key in styles.Keys) {
 582:                     control.Style.Add(key, styles[key]);
 583:                 }
 584:             }
 585:         }
 586:     }
 587: }

 

There are a couple of things to notice about my adapter compared to the sample code in the ASP.NET 2.0 CSS Friendly Control Adapters website.  First, while the sample code does all of the rendering for the Login control manually, I don't.  I'm doing what I did in my custom template and creating child ASP.NET server controls and adding them to a container Control object.  The reason why I'm using server controls is that the paths for hyperlinks may be application virtual paths that need to be resolved.  Also, there are validation controls that are going to have JavaScript to be executed on the client side that needs to be registered.  I just decided to go the easy route.

My template works using the code on lines 36 and 37 in the listing above.  On line 36, I'm clearing out the child controls of the Login control.  I do this because by the time that the adapter is called, the Login control would have already created its default template and those controls will already be in the Controls collection of the Login control.  If I add my template as a child of the Login control, then there will be duplicate user name, password, remember me, failure text, and login button controls in the Controls collection.  Once the Controls collection is cleared, I add my custom template as a new child control of the Login control.

On line 117, I do the rendering of the contents of the Login control.  You can see that I perform the rendering using the Control.RenderControl method.  I use this approach to completely render the template, including all controls, text, and JavaScript event handlers for the Login form.  When you combine this call with the fact that all of the controls are children of the Login control and thus attached to the host Page object, everything renders correctly and all of the client-side JavaScript validation and event handlers are hooked up correctly.

The last piece is removing the containing TABLE element and replacing it with a containing DIV element.  This is handled by the RenderBeginTag method starting on line 89.  I render a containing DIV element, complete with the CSS class and styles that have been declared on the Login control.

The rendered HTML output using this adapter is shown below:

image

As you can see from the DOM tree in the IE Developer Toolbar, we have completely eliminated the table-based layouts provided by the standard Login control rendering.  We now have a containing DIV and our standard template, and using the adapter in our website, this will be the default rendering behavior for the Login control across all of our application's themes.

One last question that you may have is will this Login adapter prevent you from using my previous technique for creating theme-specific layouts for the Login form?  The answer is that it won't.  You can still use the Login.LayoutTemplate property to customize the layout of the Login control.  If you look at line 29 in the source code listing above, you'll see that I'm looking to see if a template has been stored in the Login.LayoutTemplate property.  If a custom template has been provided, then my adapter will use that template to render the contents of the Login control.  The adapter will still render the containing DIV instead of the default TABLE elements, however.

With this done, all that we need to do now is build the CSS for our themes and style the Login control.  Theme away!

LoginAdapter.cs (21.83 kb)



Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Related posts

Comments

February 6. 2009 21:38

Just found this sample and it was exactly what I was looking for. However I found one potential issue that I cannot get to work. The RememberMe feature does not appear to work. If I un-bind the adapter in the .browser file, the login control will either remember or not-remember the last username based on the checkbox. Additionally, the User.Identity.Name has the correct username on subsequent pages after login. However, when I bind in the above adapter, the User.Identity.Name always is blank (or the last saved value from when the adapter was not bound and the rememberme was used.) Is there something missing from the adapter logic to allow this feature to work?
|

Tim George

February 7. 2009 03:31

I just figured it out...
In the login_Authenticate method, the authentication occurs based on the values of the controls -- the login control properties are never used/updated. I remember seeing that the password is read-only, but for the purposes of remembering who was last logged in, only the UserName and RememberMeSet attributes need to be updated.
Therefore I added the following lines @ position 229 of the code (lessons learned version -- sorry these comments should have been on that page instead):

TextBox uname = (TextBox)container.FindControl("UserName");
login.UserName = uname.Text;

CheckBox remember = (CheckBox)container.FindControl("RememberMe");
login.RememberMeSet = remember.Checked;
|

Tim George

September 20. 2009 13:38

i like
|

Tiffany Necklaces

October 3. 2009 10:13

art is a lie that tells the truth ugg classic cardy. Humor has been well defined as ugg calssic thinking in fun while feeling in earnest.The decline of literature indicates the decline of a ugg sale online nation ; the two ugg bailey button keep in their ugg classic min downward tendency. http://www.olugg.com/
|

ugg boots sale

October 3. 2009 11:01

cheap nokia phones life is the art of drawing nokia 8800 on sale sufficient conclusions from insufficient premises.Better to light one nokia 8800 candle than to curse the darkness. We can't all be apple heroes. Fear not that the life shall come to an end, but rather fear that it shall never have a ghd beginning. http://www.shopeday.com/
|

Bose headphones

October 3. 2009 11:43

<a href="http://www.ggaol.com/">unlock nokia mobile</a> it is better to waste one's youth than to do noting <A href="www.ggaol.com/...a-mobiles-c-4.html">nokia mobiles</A> with it at all. One <a href="http://www.ggaol.com/specials.html">nokia sirocco</a> thing I know.If a jewel falls into the <A href="www.ggaol.com/...eadphones-c-10.html">bose headphones</A> mire, it remains as precious as before; and though dust should ascend to <a href="http://www.ggaol.com/">buy cheap nokia 8800</a> heaven, its former worthlessness will not altered.

|

nokia mobiles

October 8. 2009 04:06

Fear not that the chi flat iron life shall come to an end, but rather fear that it shall never have a ghd iv dark styler beginning. ghd iv kiss styler it is while you are patiently toiling at the little tasks of ghd hair iron life that the meaning and shape of great whole ghd mk4 iron of life dawn on you. http://www.ghdhairon.com/
|

GHD Hair Iron

October 27. 2009 01:52

<h2 align="center">The warmest ugg boots</h2>
<p>While access to source sheepskin is more and more expensive, in order to create the warmest and highest quality <a href="www.goodugg.co.uk/...short-c-160.html">ugg" rel="nofollow">www.goodugg.co.uk/...short-c-160.html">ugg boots</a>, we are still at the cost of manufacturing. In addition to perfect our sheep fleece, the inner is extremely dense, so that your feet are wrapped in a fleece and fluffy soft and warm.</p>
<p>--&gt;<strong>The improvements come with toes</strong><br />
Traditional <a href="www.goodugg.co.uk/...ots-p-24145.html">ugg boots</a> are cosy, soft and easy to wear. The inner sheepskin fibres are denser, plusher and much softer on your skin, and the sheepskin definitely won't scratch your skin or cause odour problems associated with tradition ugg boots. You can actually see the difference very clearly.<br>
--&gt;<strong>Designed for bare toes</strong><br />
This premium graded sheepskin <a href="www.goodugg.co.uk/...-tall-c-162.html">ugg" rel="nofollow">www.goodugg.co.uk/...-tall-c-162.html">ugg tall boots</a> creates the worlds warmest and cosiest fashion icon. The thicker inner fleece acts as natural air conditioner, circulating and maintaining body warmth on colder days. Designed for bare feet our boots offer the ultimate indulgence in luxury and comfort.<br>
--&gt;<strong>Actively remove foot odor</strong><br />
A thicker spongy fleece of <a href="www.goodugg.co.uk/...short-c-160.html">ugg" rel="nofollow">www.goodugg.co.uk/...short-c-160.html">ugg classic short</a> will circulate more air. Synthetic ugg boots created from lower grade sheepskin do not posses this ability to circulate air as effectively as merino sheepskin. <br>
--&gt;<strong>Comfortable all year round</strong><br />
The worlds warmest <a href="www.goodugg.co.uk/...-tall-c-167.html">ugg boots</a> are also the worlds coolest, but only when your feet need chilling of course! Our thicker fleece allows your toes to enjoy their favourite footwear all year round.<br>
--&gt;<strong>Fashion Laws</strong><br />
The sheepskin boots and ugg boots women choose are predominantly of the <a href="www.goodugg.co.uk/...p-23487.html">classic tall ugg boot</a> or classic short styles. Proving that sheepskin boots truly are unisex.</p>
<p>We have to savor the hidden glamour of Ugg Boots with a different angle.</p>
<p>All rights reserved, reprint, please specify source comes from <a href="www.goodugg.co.uk/.../a> --<a href="www.goodugg.co.uk/...on-c-178.html">bailey button</a>,<a href="www.goodugg.co.uk/...ridge-c-321.html">ugg knightsbridge boots</a>,<a href="www.goodugg.co.uk/...rdy-c-161.html">cardy boots</a>,<a href="www.goodugg.co.uk/...-tall-c-162.html">ugg" rel="nofollow">www.goodugg.co.uk/...-tall-c-162.html">ugg tall classic</a></p>
|

angelia110

November 8. 2009 19:42

We are the best online sales for the online cheap goods . Here you can have a large of choices of kinds Ugg Boots,Converse Shoes,Timberland Boots,puma shoes,Nike Shox Shoes ,Nike Dunk SB Shoes,Nike Air Max,Links Of London,Tiffany Jewelry,Dior Handbags, NHL Jerseys,Cartier Watches, 8GB Mp4 Players,Bluetooth Car DVDs. All our cheap online cheap goods are high quality and original packages, and best service. We offer our customers the best service, 7 days arrive at your door.Enjoy your easy and happy shopping with us.
Welcome to our website:http://www.cheapgoodssale.com
|

UGG Classic

November 26. 2009 05:28

<a href="http://www.asicscloset.com/">mens onitsuka tiger shoes</a>
<a href="www.asicscloset.com/...0.html">Comfortable asics onitsuka tiger mexico 66 Shoes</a>
<a href="www.asicscloset.com/...-46_53.html">Sporty Men's Asics Onitsuka Tiger Midrunner Shoes</a>
<a href="www.asicscloset.com/...c-46_52.html">Cheap asics onitsuka tiger mini clubman shoes</a>
|

asics onitsuka

November 26. 2009 05:29

<a href="www.toamazingstore.com/...s-c-52.html">UGG" rel="nofollow">www.toamazingstore.com/...s-c-52.html">UGG Cardy Grey</a>
<a href="www.toamazingstore.com/...s-c-52.html">UGG" rel="nofollow">www.toamazingstore.com/...s-c-52.html">UGG Classic Cardy</a>
<a href="www.toamazingstore.com/...-c-40.html">UGGS Crochet Boots</a>
|

ugg classic

November 28. 2009 16:45

If a man join together to pursuit knowledge, no one can take it from him.cheap nfl jersys Books are to human beings which as nba jerseys sale emory to the individuanl nhl Jerseys.We cannot change anything mlb jerseys unless we learn and accept it, Damn does not liberate it, it oppresses.A classic adidas jerseys book which people praise but don't read.A man dies still if he has done nothing, as one who has done football child jerseys much.Education is something? which remains ofter one has forgotten everything football jerseys he learned in school.Education is something remains ofter one has forgotten everything he learned in school.http://www.nfljerseymlb.com/
|

cheap nhl jersey sale

November 28. 2009 16:46

Sorrow is peace in my heart like the ugg 5806 boots evening among the tall upside lace uggs silent trees.if you tears when you miss the ugg 5817 style boots sun, you will also miss the stars.her eager ugg 5819 fur boots face bothers my ugg 5842 classic boots dreams as the ugg 30th anniversary rain at ugg 5691 lo pro button night.once we dreamt that we were ugg 5202 infant's erin strangers. when we wake up we find that we were to love each other's. http://www.uggboots4buy.com/
|

ugg fur boots 5817

December 4. 2009 15:44

Thanks for your sharing,It's wonderful
|

ugg classic cardy style boots

December 4. 2009 15:45

It's wonderful,welcome to my site
|

women's bailey ugg boots

December 5. 2009 07:01

Hiya, I'm starting up a website and began making rather varied content. Would you mind if I blog something about this? Of course I will give you and this post due credit.
|

Jill Cox at Apple Trees

December 5. 2009 07:28

Hiya, I've just started up a blog and in the middle of making rather varied articles for it. Do you mind if I blog about this article? Of course I will provide you and this post full acknowledgment.
|

Jenny at Apple Trees

December 12. 2009 00:37

<a href="www.realuggbuy.com/...d-p-1102.html">cheap ugg classic tall boots</a><br>
<a href="www.realuggbuy.com/...and-p-1102.html">ugg 5815 Boots on sale</a><br>
<a href="www.realuggbuy.com/...nd-p-1102.htmll">ugg classic tall chestnut</a>
|

ugg classic tall chestnut

December 16. 2009 04:29

<strong><a href="www.souggs.com/specials.html">Australia Ugg Boots</a></strong> becoming more and more popular,new style <strong><a href="http://www.souggs.com/">ugg boots</a></strong> will be the best seller for this Christmas,includes<strong><a href="www.souggs.com/products_all.html">Ugg Knightsbridge</a>,<a href="www.souggs.com/...ley-button-c-2.html">UGG Bailey Button</a></strong>;and the <strong><a href="www.shoeshotsale.com/.../strong><strong><a href="www.souggs.com/ugg-tall-5815-c-3.html">ugg classic tall boots,</a></strong> <strong><a href="www.souggs.com/...cardy-5819-c-1.html">ugg cardy boots</a></strong> also hot sale for these years,you can choose your lovely <strong><a href="www.souggs.com/products_new.html">UGGS Boots</a></strong> on shoesem.com,You can choose the <strong><a href="www.souggs.com/..._products.html">discount ugg boots</a></strong> or the newest <strong><a href="www.souggs.com/.../strong> for yourself or your friends.
|

ugg

December 19. 2009 11:07

S11217C2 Do you think I can stay timberland boots uk to become nothing to you ? Do you think I am a timberland boot automaton ? --a discount timberland boots machine without feelings ? and can bear classic timberland boots to have my morsel of bread snatched from my lips, and my drop of kids timberland boots living water dashed from my cup? Do you think, because I am timberland mens shoes poor, obscure, plain, and little, I am soulless and heartless? You think timberland 6 inch boots wrong!--I have as much timberland custom boots soul as you,--and full as much timberland roll top boots heart! And if God had timberland womens shoes gifted me with some beauty and much wealth, I should have made it as hard for you to leave me, as it is now for me to leave you. I am not talking to you now through the medium of timberland hiking boots custom, conventionalities, nor even of mortal flesh. http://www.runtimberland.com/
|

timberland boots uk

December 25. 2009 17:17

<a href="http://www.ghdhairirons.com/">ghd</a>" rel="nofollow">www.ghdhairirons.com/">ghd</a> is a good choice for people. <a href="http://www.ghdhairirons.com/">ghd straightners</a> are of high quality. you will have a brand new experience when using <a href="www.ghdhairirons.com/...ghd-c-3.html">pink ghd hair straighteners</a>.
|

bush

December 25. 2009 18:05

a href="http://www.ghdhairirons.com/">ghd" rel="nofollow">http://www.ghdhairirons.com/">ghd hair straighteners</a> always attract people's attention, since people are fond of <a href="http://www.ghdhairirons.com/">ghd" rel="nofollow">http://www.ghdhairirons.com/">ghd hair straightener</a> especially <a href="www.ghdhairirons.com/...ghd-c-3.html">pink ghd straighteners</a

|

bush

January 12. 2010 20:02

Hello, your blog's design is awesome and i like it. Your articles are pretty cool. Please continue this great work. Cheers!!!
|

adventure games

January 15. 2010 23:37

Thanks for that exceptionally astounding column!
|

Home Loan Mortgage Refinancing Loan

January 16. 2010 21:35

Could you pleasee provide more details on this topic? BTW your site is incredible. Cheers.
|

tower defence games

January 19. 2010 02:31

That is an extraordinarily awe inspiring article you've posted.
|

Download I Hope They Serve Beer In Hell Movie Free

January 20. 2010 00:19

Thank you very much for this especially great editorial!
|

Download Antichrist Movie Free Online

January 21. 2010 02:25

Thank you very much for that very awe inspiring editorial.
|

Natural Teeth Whitening

January 22. 2010 16:48

That is an extremely good quality post you've posted!
|

Bad Credit Home Refinance

January 23. 2010 06:09

100121SLLPJWhen he heard of the ugg classic cardy sale, cold may be in your mind, you're ready to once.if Of lo pro classic tall uggs, you should never be afraid we can not put the difficulties classic tall boots sale.it If you protect your feet well, you will drop your warmth and comfortable tall uggs sale.so Shoes should be of some help you through the cold winter.classic tall boots sale can be done.for many individuals, classic cardy ugg boots provide the perfect insulation and comfort Months.ugg winter boots are warm and comfortable.these two factors It is very important women's lo pro classic tall. http://www.officialuggs.com/
|

cheap ugg boots

January 26. 2010 13:22

This is a fantastically cool column you have posted!
|

laser hair removal dangers

January 27. 2010 11:43

This is an exceedingly groovy post you've put up here!
|

refinance home loan mortgage debt consolidation

January 27. 2010 14:32

Hi, I applaud your blog for informing people, very interesting article, keep up it coming Smile
|

cheap jewelry

January 28. 2010 02:56

http://www.tiffanycome.com/ Tiffany & Co
http://www.tiffanycome.com/accessories-c-7.html Tiffany Jewellery
http://www.tiffanycome.com/bangles-c-5.html tiffany and co
http://www.tiffanycome.com/bracelets-c-2.html Tiffany Bracelet On Sale
http://www.tiffanycome.com/cufflinks-c-6.html links of london
http://www.tiffanycome.com/earrings-c-3.html tiffany co london
http://www.tiffanycome.com/necklaces-c-1.html links london
http://www.tiffanycome.com/rings-c-4.html Tiffany Rings
http://www.tiffanycome.com/watches-c-8.html tiffany and company
|

tiffany jewellery

January 28. 2010 10:17

Thank you very much for that awfully good quality editorial.
|

best tooth whiteners

January 28. 2010 13:32

Hi, I applaud your blog for informing people, very interesting article, keep up it coming Smile
|

guess watches

January 31. 2010 02:59

Hey mate, cool blog post.. please keep it up...
|

business website hosting

January 31. 2010 08:44

Hey admin, great article.. pls continue this awesome work.
|

flash online games

February 10. 2010 07:47

Hey you guys, the German website for Skate 3 just launched - Go check out skate3.de! I can't speak the language that well lol but they even have a "Pre-order Skate 3 and save Haiti" donation campaign running that I really like. What do you think?
|

Skate 3

February 11. 2010 17:47

Good thread. Cheers!!
|

accident lawyer

February 11. 2010 18:11

Good thread. Cheers!!
|

accident lawyer

February 12. 2010 04:42

Good thread. Cheers!!
|

accident lawyer

February 12. 2010 08:49

Good thread. Cheers!!
|

personal injury

February 12. 2010 09:37

Good posts. Thanks!
|

website building

February 12. 2010 09:49

Good thread. Cheers!!
|

injury attorney

February 12. 2010 09:58

Good posts. Thanks!
|

website building

February 12. 2010 10:03

Very informative posts, glad I found this site. Thanks!
|

std symptoms

February 12. 2010 10:23

Very informative posts, glad I found this site. Thanks!
|

std symptoms

February 12. 2010 10:23

Very informative posts, glad I found this site. Thanks!
|

std testing

February 12. 2010 10:29

Good thread. Cheers!!
|

personal injury

February 12. 2010 10:34

Hey buddy I've really had a blast reading up your blog I've found them really interesting, Can't wait for your next post! Speak again soon =]
|

Cheap DVD Box sets uk

February 12. 2010 10:45

Good posts. Thanks!
|

website building

February 12. 2010 10:52

Good posts. Thanks!
|

website hosting

February 12. 2010 15:14

Great thing. I was looking for places to advertise my blog… Almost all major directories ban blogs, so this page came in handy. I just bookmarked it but how do I get your RSS feeds to work with my browser(Chrome)?
|

Dvd Burner

February 12. 2010 15:23

Considerably, the blog post is really the best on this valuable theme. I harmonize with your conclusions and will eagerly look forward to hear your next updates. Saying tnx will not just be adequate, for the exceptional lucidity in your writing. I will immediately grab your rss feed to stay informed of any updates. Genuine work and much success in your business efforts! Cheers, Samuel.
|

acai

February 12. 2010 15:28

Good info. Don't let the homebuyer tax credit pass you by!
|

homes for sale seminole largo

February 13. 2010 22:51

Great thing. I was looking for places to advertise my blog… Almost all major directories ban blogs, so this page came in handy. I just bookmarked it but how do I get your RSS feeds to work with my browser(Chrome)?
|

Pdf Search Engine

February 13. 2010 23:07

Enjoyed the posts..
|

fraternity

February 13. 2010 23:19

Enjoyed the posts..
|

fraternity

February 14. 2010 18:49

Great thread. Enjoyed the posts..
|

aloe cure

February 14. 2010 19:02

Great thread. Enjoyed the posts..
|

aloe cure

February 18. 2010 19:28

Wow! Thank you! I always wanted to write in my site something like that. Can I take part of your post to my blog?
|

Partyservice in Siegen

February 18. 2010 20:44

Good thread. Cheers!!
|

flowers

February 18. 2010 20:45

Good thread. Cheers!!
|

gardening

February 19. 2010 17:04

Enjoyed the posts..
|

sorority

February 19. 2010 17:22

thanks !! very helpful post!
|

Skate 3 German News

February 19. 2010 19:10

I really like reading your blog as the posts are so simple to interpret and follow. Outstanding. Keep up the great work. Ciao.
|

positive parenting

February 21. 2010 00:52

|

RILEY

February 21. 2010 08:44

Enjoyed the posts..
|

fraternity

February 21. 2010 08:46

Enjoyed the posts..
|

sorority

February 22. 2010 12:05

Hey, good looking layout I see here. I really liked scanning this piece. I by chance ran across it this afternoon and thought it was interesting, so it prompted me to leave a comment. I believe there will be others who probably have the same view about this topic. So, I’ll visit again when I have extra moments to view more. This is kinda weird, but I was just calling my mom regarding this topic, so this info was just what I needed. I'm pumped I found this page, it has good energy. I will favorite this website and subscribe to the feed as well. Thanks have a good one.
|

Neto Amarillo

February 23. 2010 08:45

I was very gratified to find this site. I needed to thank you for this extraordinary read. I definitely relished every little bit of it and I have you bookmarked to find out new stuff you post.
|

Meg Auplods

February 23. 2010 13:36

Hi there, I found your blog via Google while searching for first aid for a heart attack and your post looks very interesting for me.
|

Skate 3 Screenshots

February 24. 2010 07:27

Enjoyed the posts..
|

fraternity

February 25. 2010 09:14

Awesome post. Thanks! If your having problems with your colon come to <a href="http://www.coloniccleansing.info" rel="nofollow">www.ColonicCleansing.info</a>
|

colonic cleanse

February 25. 2010 09:49

Excellent post. Thanks! Read our reviews of colon cleansing at <a href="http://www.coloniccleansing.info" rel="nofollow">www.ColonicCleansing.info</a>
|

colonic cleansing

February 25. 2010 12:47

Awesome post. Thanks! Come read our blog about monte carlos at <a href="http://www.classicmontecarlos.com" rel="nofollow">classic monte carlos</a>
|

classic monte carlos

February 25. 2010 17:11

I really liked your article. Please keep them coming. Regards from Australia...
|

Wayne

February 27. 2010 06:02

Do you think you have STD symptoms? See pictures here http://www.std-symptom.info and to find STD testing.
|

std testing

March 2. 2010 06:23

Enjoyed the posts..Great site for fraternity and sorority gear http://www.greekvibes.com
|

sorority

March 2. 2010 06:41

The Professional 600 TM has a powerful motor that is crafted for commercial-style performance, the direct drive, all-steel gear transmission delivers unyielding power for professional results. The mixer can effectively mix up to 14 cups of all-purpose flour per recipe and produce up to 8 pounds of mashed potatoes. The Soft Start mixing feature helps minimize ingredient splatter and flour puff with gentle acceleration to selected speed after start-up. Electronic Speed Sensor monitors operation to maintain precise mixer speed. Commercial-style motor protection automatically shuts off the mixer when overload is sensed to protect the motor. Durable all-steel gears provide reliable service. The mixer includes a 6-quart-capacity stainless-steel mixing bowl with a contoured handle, as well as a professional wire whip, a burnished flat beater, a spiral dough hook, and a one piece pouring shield. Its multipurpose attachment hub with a hinged hub cover flips open to allow for easy installation of attachments. The Professional 600 Series bowl-lift design raises the bowl into the mixing position, and its solid, truly seamless one-piece motor head design is easy to clean and comes in a variety of fashionable colors.
|

kitchenaid professional 600

March 2. 2010 09:50

Enjoyed the posts..Great site for fraternity and sorority gear http://www.greekvibes.com
|

sorority

March 2. 2010 12:41

The Professional 600 TM has a powerful motor that is crafted for commercial-style performance, the direct drive, all-steel gear transmission delivers unyielding power for professional results. The mixer can effectively mix up to 14 cups of all-purpose flour per recipe and produce up to 8 pounds of mashed potatoes. The Soft Start mixing feature helps minimize ingredient splatter and flour puff with gentle acceleration to selected speed after start-up. Electronic Speed Sensor monitors operation to maintain precise mixer speed. Commercial-style motor protection automatically shuts off the mixer when overload is sensed to protect the motor. Durable all-steel gears provide reliable service. The mixer includes a 6-quart-capacity stainless-steel mixing bowl with a contoured handle, as well as a professional wire whip, a burnished flat beater, a spiral dough hook, and a one piece pouring shield. Its multipurpose attachment hub with a hinged hub cover flips open to allow for easy installation of attachments. The Professional 600 Series bowl-lift design raises the bowl into the mixing position, and its solid, truly seamless one-piece motor head design is easy to clean and comes in a variety of fashionable colors.
|

kitchenaid professional 600

March 4. 2010 04:14

I like watching movies online, it is way cheaper than going to the theaters.
|

watch movies online

March 4. 2010 05:41

I enjoy watching movies online, it is way easier than going to the theaters.
|

hd movies

March 9. 2010 05:57

I enjoyed that! I've had a lovely time looking around your site, you've done a great job, well done.
|

Rich Guru Poor Guru

March 9. 2010 14:34

I enjoyed This blog very much. Thank you for the well thought out and presented information
|

new credit file legally

March 10. 2010 19:05

This webpage is very understanding, I very much enjoy reading your article, i hope you carry on posting articles, I'm subcribe to your blog
|

watch movie clips

Add comment


 

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

March 11. 2010 23:34

|