다음을 통해 공유


Solution Architecture For The Masses. Step 4: Design Your Presentation Layer – Part II

 Alik Levin    This post is a follow up of Part I. I am following the Web Application frame outlined in Web Application Archetype. In Part I I covered Authentication, Authorization, Caching, Exception, Management, Logging & Instrumentation. In this post I will cover the rest of Web Application categories – Navigation, Page Layout (UI), Page Rendering, Presentation Entity, Request Processing, Session Management, and Validation

Quick Resource Box

Navigation

To visualize navigation I am using ASP.NET’s built-in treeview control bound to web.sitemap file and SiteMapPath [bread crumb] control for visualization. The navigation controls located in dedicated ACSX files that are placed inside formatted Master Page – more on that in Page Layout (UI) section. Follow these steps:

  • Add site map xml file to the root folder. Right click on the root folder of the web project and add new item, site map xml file. Site map located under Web category:

image

  • Edit the XML site map file to reflect the desired site structure, for example:

<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="https://schemas.microsoft.com...." >     <siteMapNode url="deafault.aspx" title="Home" description="">         <siteMapNode url="Restricted/UC1.aspx" title="UC1" description="" />         <siteMapNode url="Restricted/UC2.aspx" title="UC2" description="" />     </siteMapNode> </siteMap>

  • Create two ASCX controls in Controls folder, Header.ascx and Sidebar.ascx.

image

  • Double click the Header.ascx file, switch to design mode, and drag SiteMapPath on it:

image

  • Double click Sidebar.ascx file, switch to design mode, and drag TreeView control on it, follow the wizard to configure it to use the XML site map:

image 

  • Add output caching to the both ASCX controls:

<%\@ OutputCache Duration="3000" VaryByParam="None"%>

Page Layout (UI)

Page layout is based on ASP.NET built-in Master Pages. Think of Master Page as a “server side frameset”. The page assembled on the server from the Master Page and the controls it hosts, rendered as a single HTML output and sent for rendering in the browser. The advantages are:

  • Maintainability. Visual components implemented as a separate ASCX files can be reused and changed separately from the rest of the UI improving maintainability.
  • Performance. Breaking the UI into separate parts allows to partially cache relatively static components like side bar, header, search control, and other. The technique known as partial Output Caching, more info -  Caching Explained. The fact that the page assembled and cached on the server, either partially or as a whole, helps to avoid heavy manipulation on the client using JavaScript and CSS which in some cases can lead to sever responsiveness [performance] problems, more info - Understanding Internet Explorer Rendering Behaviour.

Follow these steps to implement page layout based on Master Page:

  • Right click on the root folder and add new item – Master Page. Master Page located under Web category. Call it Base.Master:

image

  • Implement the HTML layout using HTML table. Note, though that from rendering perspective table based formatting is slower than using CSS. Keep in mind that the fact that the page is broken into visual components will allow to further implement it using CSS without breaking it. From the guide:
    • Use Cascading Style Sheets (CSS) for layout whenever possible.
    • Use table-based layout when you need to support a grid layout, but remember that table-based layout can be slow to render, does not have full cross-browser support, and there may be issues with complex layout.
    • Use a common layout for pages where possible to maximize accessibility and ease of use.
    • Use master pages in ASP.NET applications to provide a common look and feel for all of the pages.
    • Avoid designing and developing large pages that accomplish multiple tasks, particularly where only a few tasks are usually executed with each request.
  • While in the Master Page, from the menu choose Table->Insert Table. Make it two rows and two columns with one of them spanning over the two rows. Notice the ContentPlaceHolder – this is where the actual dynamic content of the pages will be rendered – more on it in Page Rendering section below:

<table class="style1">     <tr>         <td colspan="2" align="left" valign="top">         </td>     </tr>     <tr>         <td align="left" valign="top">         </td>         <td align="left" valign="top">     <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">     </asp:ContentPlaceHolder>         </td>     </tr> </table>

  • Switch to the Design mode and drag Sidebar.acsx on the left side of the table and the Header.ascx control to the upper part of the table. The result should look as follows:

image

  • ContentPlaceHolder depicted above will hold the actual pages that “inherit” from the Master Page.

Page Rendering

Page rendering is the process of generating and displaying HTML in the browser. It usually involves displaying dynamic data from some data source such as SQL Server.

For example, if I am required to present all transactions I have made, I’d follow these steps:

  • Add new page to Restricted folder [access to the page should be authorized – see Authorization in Part I]. To do so right click on the Restricted folder, then Add ->New Item and choose Web Form  using Master Page. Name it Transactions. Click Add. Specify available Master Page. Click OK.
  • From the tool box, under  Data category, drag ListView control on the placeholder area. 
  • Performance degrades with the number of items presented in the grid. Use paging to improve the responsiveness for the pages that present many rows. More info on paging - The DataPager Control, How To: Page Records Using AJAX, How To: Page Records in .NET Applications. Add DataPager control. The result should look similar to this:

image

  • Configure the DataPager to control  the ListView

<asp:DataPager ID="DataPager1"                runat="server" PageSize="3" PagedControlID="ListView1">

  • Next step is to add the actual data to this view, the presentation entity as described in the next section.

Presentation Entity

Presentation entity is the data being presented on the page. In my case it is TransactionInfo that was implemented together with its business services in Step 2. In my case I get the list of the transactions generated by corresponding business services component and then bind it to the ListView control for presentation. Also I handle the paging event by calling ListView1_PagePropertiesChanging function [with little help from this post]. Code behind looks similar to the one that follows this paragraph. Notice IsPostBack check to make sure I do not run this code in case of postback [POST]. In this case the presentation re-generated from the ViewState so I save on CPU cycles and improve performance with regards to response time and resource utilization:

protected void Page_Load(object sender, EventArgs e) {     if (!IsPostBack)     {         BindListView();     } } protected void ListView1_PagePropertiesChanging( object sender,                     PagePropertiesChangingEventArgs e) {     this.DataPager1.SetPageProperties(e.StartRowIndex, e.MaximumRows, false);     BindListView(); } void BindListView() {     List<TransactionInfo> transactions = TransactionServices.GetCurrentUserTransactions();     ListView1.DataSource = transactions;     ListView1.DataBind(); }

In this specific case all the input was available immediately in the request. In fact GetCurrentUserTransactions would use IPrincipal and IIdentity interfaces to access HttpContext.Current.User to locate the actual current user internally as described in Part I – that is why I do not pass any parameters to it. In some cases I will need to provide parameters submitted from different controls such as in the case of search scenario. In that case the parameter submitted by one ASCX control and the rendering of the result performed by other control or page. This is covered in the next section, Request Processing.

Request Processing

When looking at the guidelines for request processing the recurrent theme is separating user interface, processing, and the data. The patterns that mentioned are MVC and MVP. Following these patterns achieves loosely coupling between the components which increases maintainability. In other words, when one component changes it does not affect the other one. One recurrent scenario is search. I followed these steps to implement my search:

  • Create new user ASCX control, SearchInput.ascx, by right clicking on the Controls folder, Add-> New Item…-> Web User Control.
  • Place the text box and the button on the SearchInput.ascx.
  • Double click the button and add the following code to the even handler. That way anyone who’d need to use the search criteria would just check the Page.Items collection without even knowing who put it there. It follows the principle of decoupling [or loosely coupling, or pub/sub specifically]:

protected void btnSearch_Click(object sender, EventArgs e) {     string searchCriteria = txtSearch.Text;     Page.Items.Add("SearchCriteria", searchCriteria); }

  • Add new page under Restricted folder, name it AccountSearchResult.aspx, switch to design mode.
  • Drag onto it the SearchInput.ascx control.
  • Drag onto it Label control.
  • Drag onto it DatGrid control. The result should look similar to this:

image

  • Add the following code the page’s prerender event:

protected void Page_Prerender(object sender, EventArgs e) {     string searchCriteria = Page.Items["SearchCriteria"] as string;     if (null != searchCriteria)     {         Label2.Text =                   string.Format("Your search for {0} generated the following results: ",searchCriteria);         List<AccountInfo> accounts = AccountServices.FindAccountsBySearchCriteria (searchCriteria);        GridView1.DataSource = accounts;        GridView1.DataBind();     } }

  • The code assumes there is a search criteria in page’s items collection, and if it’s there then it executes the search and binds the results to the grid. The rendered result should look as the following:

image

In this example the page shows the results that and it’s completely decoupled from the search input control that accepts the input.

Session Management

The guide stresses the importance of session management with regards to scalability and performance: “When designing a Web application, an efficient and secure session-management strategy is important for performance and reliability.” Here are variations and implications:

  • Use in-proc Session would require me to configure my load balancer for sticky session. Generally it’s the most common approach and works fairly well for most scenarios. The biggest downside is when one of the servers dies the state of user dies too, and the user loses his state. For example, if the user was in the middle of transaction – it all lost.
  • Use out of proc state. This approach would eliminate the risk of losing the state since in this case the state is usually stored in DB. The downside is that the performance gets hurt as a result of extra N/W hope, serialization, and security checks. I observed few cases where the performance hit was significant.

For my case I will be using in-proc Session state. It’s easy to implement, I do not plan to store large data to avoid memory pressure, serialization cost, and recycles. I am taking the risk of the case when the end user loses the state due to the server failure since my scenarios do not assume massive data input.

Validation

Input and Data Validations are extremely important aspects that affect system’s security and reliability. Making sure only sanitized input gets in prevent unnecessary exceptions that eat up CPU cycles. It also helps prevent injection attacks. Making sure the system produces encoded output prevents Cross Site Scripting [XSS] attacks that usually end up with identity theft. For input and data validation I will use:

  • ASP.NET built in validation controls.
  • My encoding security services I have implemented in Step 3.

After reviewing the code I have produced so far, following are the changes that need to be done:

  • Add HTML encoding when echoing the input in AccountSearchResults.aspx:

Label2.Text = EncodingServices.HtmlEncode( string.Format ("Your search for {0} generated the following results: ",searchCriteria));

  • Encoded the output in Transactions.aspx:

<%# EncodingServices.HtmlEncode(((Entities.TransactionInfo)Container.DataItem).TransactionAccount)%> ||

<%# EncodingServices.HtmlEncode(((Entities.TransactionInfo)Container.DataItem).TransactionAmount.ToString())%> ||

...

  • Added validation control to the search input control, SearchInput.ascx:

<asp:RegularExpressionValidator  ID="RegularExpressionValidator1"                             runat="server"                             ControlToValidate="txtSearch"                             ErrorMessage="Invlaid input"                             ValidationExpression= "&quot;^[a-zA-Z'.\s]{1,40}$&quot; ">  </asp:RegularExpressionValidator>

  • To make sure the outcome of the validation control is taken into account I need to add the Page.IsValid in my AccountSearcResults.aspx before rendering the results in Prerender event:

if (!Page.IsValid) {     //THE BETTER WAY WOULD BE TO USE     //VALIDATION SUMMARY CONTROL     throw new ApplicationException("Invalid input provided."); }

Related Books