13 Dec 2009 @ 5:00 PM 

Over time while using ASP.NET I’ve collected a pretty good handful of best practices that I try to employ on my projects – most of them are things that will simplify the ASP.NET development experience, solutions to common problems, or tips that will just make your life easier.  Most of the best practices are only applicable to WebForms, but some are applicable to ASP.NET MVC as well.

  • Don’t write .NET code directly in your ASPX markup (unless it is for databinding, i.e. Eval statements). If you also have a code behind, this will put your code for a page in more than one place and makes the code less manageable. Put all .NET code in your code-behind.  Things can get complex and difficult to debug very quickly when you’re looking at code executing in two different places.
  • SessionPageStatePersister can be used in conjunction with ViewState to make ViewState useful without increasing page sizes. Overriding the Page’s PageStatePersister with a new SessionPageStatePersister will store all ViewState data in memory, and will only store an encrypted key on the client side.  This will make your pages smaller and download faster if you have a lot of ViewState data for some reason, however it will increase your memory usage on the server – so tread carefully.  See example below for how to use SessionPageStatePersister.
public override PageStatePersister GetStatePersister() {
return new SessionPageStatePersister(this);
}
  • Create a BasePage that your pages can inherit from in order to reuse common code between pages.  Simple object oriented design principles – if you have common functions between pages, like security for example – put it in a base class that inherits from System.Web.Page, and have your pages inherit from that base page.
  • Create a MasterPage for your pages for visual inheritance.  Don’t use ASP server-side includes.  Pages with vastly different visual styles should use a different MasterPage.  Don’t use a Master page for code inheritance.
  • Make use of the ASP.NET Cache in order to cache frequently used information from your database.  Build (or reuse) a generic caching layer that will wrap the ASP.NET Cache.  If you’re loading the same list from the database into a drop down every time a page loads, you should be pulling that list from the cache based on how dynamic it needs to be.
  • Wrap ViewState objects with Properties on your Pages to avoid development mistakes in spelling, etc. when referencing items from the ViewState collection.  For example, you should only have ViewState["key"] once in your page per property.  See example below.
private int SampleId
{
get { return ViewState["SampleId"] == null ? 0 : (int)ViewState["SampleId"]; }

set { ViewState["SampleId"] = value; }
}

  • Avoid putting large objects and object graphs in ViewState, use it mainly for storing IDs or very simple DTO objects.  This is the reason people always complain about huge viewstate – they’re storing something like DataSets in ViewState (terrible idea).  If you stick to small objects with a limited number of properties or just integer IDs, your ViewState data will not be unmanageably large and ViewState is totally usable.
  • Wrap the ASP.NET Session with a SessionManager class to avoid development mistakes in spelling, etc. when referencing items from Session.  Just another way to cut down simple development mistakes.
  • Make extensive use of the applicationSettings key/value configuration values in the web.config – wrap the Configuration.ApplicationSettings with a class that can be used to easily retrieve strongly-typed configuration settings without having to remember the keys from the web.config.  If you have settings, behaviors, etc. that need to change between different deployments of your application, those should be control via settings in the web.config.  For example, we’ll often get requests like ‘We want feature X to go live at the end of the month” – so build, test, and deploy the update ahead of time.  But, add a web.config value that controls whether or not the feature appears i.e. FeatureXEnabled=”False”, on the day of go live just flip it to “True”.
  • Avoid the easiness of setting display properties on your UI controls, instead use CSS styles and classes – this will make your styles more manageable.  Just a general web development best practice.
  • Create UserControls in your application in order to reuse common UI functionality throughout your pages. For example, if a drop down list containing a collection of categories will be used in many places in the site – create a CategoryPicker control that will data bind itself when the page is loaded.  This is my #1 time-saving best practice, yet I’m always surprised how often I see the same drop down list with the same data getting used the same way on 20 different pages – yet the same type-unsafe databinding logic is duplicated 20 times!
  • Use Properties on your UserControls to setup things like default values, different displays between pages, etc. Value type properties can be defined on your UserControls and then be set in your ASP.NET markup by using class level properties on UserControls.  This is a great way to get even more mileage out of reusing your UserControls – watch out for increased complexity of your UserControl logic though.
  • Make use of the ASP.NET validation controls to perform simple validations, or use the CustomValidator to perform complex validations.
  • Create an user-friendly error handling page that can be redirected to when an unhandled exception occurs within your website.  Log any exceptions that come to this page.  The redirection can occur via the Page_Error event in your Base Page, the Application_Error event in your Global.asax, or within the error handling section in the web.config.  Basically, whichever method you pick – make sure you’re not letting any exceptions go unhandled or unlogged!
  • When working with pages that use a highly dynamic data driven display, use the 3rd party (free) DynamicControlsPlaceholder control created by Denis Bauer to simplify the code needed to save the state of dynamically added controls between postbacks.  This little control has saved me countless hours of pain in creating pages with highly dynamic UserControls.  One gotcha – if you use event handling delegates in a UserControl, you have to hook them up on every postback, little messy but not a big deal though usually.  Event handlers are the only “state” that isn’t saved between postbacks if you use this control.
  • Turn ViewState off on controls and UserControls that don’t need it.
Posted By: admin
Last Edit: 14 Dec 2009 @ 06:21 PM

EmailPermalinkComments (6)
Tags
Tags: ,
Categories: Uncategorized
 12 Nov 2009 @ 9:36 PM 

Ran into an interesting problem yesterday where a few months ago we helped a client redesign an ASP.NET web application to fit it into an iframe within their CMS rather than being a standalone site.  Easy enough task.  Testing is completed and site is rolled out.

Now, several months down the road after the application has been iframe’d and in production – one random feature of the application is unexpectedly breaking, but it doesn’t make any sense – the only way the behavior could possibly occur would be that an object retrieved from Session is coming back as null, which turned out to be the case.  The browser was somehow losing the ASP.NET Session cookie.  Furthermore, the feature was working fine in Firefox but not in Internet Explorer, very strange.

The problem was that Internet Explorer will not accept cookies from a page within an iframe where the domain name is different from the top level page.  So, the url of the iframe’d page was www.clientsite1.com and the url of the page hosting the iframe was www.clientsite2.com.

To get around this, you need to add a P3P Compact Policy to your HTTP responses.  P3P is a protocol that allows websites to pass information to the browser regarding their intent to use information collected from the user.  Internet Explorer is the only browser that implements the protocol, and only using it for cookie blocking at that.

To add a P3P in ASP.NET that will allow your cookies to be accepted by the browser from a different domain from within an iframe, add this block of code to your Global.asax.

protected void Application_BeginRequest(object sender, EventArgs e)
{
     HttpContext.Current.Response.AddHeader("p3p","CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");
}
Posted By: admin
Last Edit: 12 Nov 2009 @ 10:25 PM

EmailPermalinkComments (0)
Tags
Tags:
Categories: Uncategorized
 02 Nov 2009 @ 2:35 PM 

I recently ran into a situation where a client wanted to place their public facing ASP.NET website behind Oracle SSO to allow their customers to log in via their existing Oracle SSO accounts, yet also allow anonymous users to use the application without logging in.  We could have done a simple LDAP integration, but the client also wanted users to not have to log into the application if they’re already logged into their Oracle Portal account.

First step, we had to determine how to get Oracle SSO running on Oracle Application Server (OAS) to protect an application running on a separate IIS web server. In order for an ASP.NET application to be able to get user credentials from Oracle SSO, Oracle SSO has to run “in front of” your application – i.e. something has to intercept the browser request on the way to your web app, decrypt their SSO session cookie, and inject an HTTP header containing the the user’s username that your ASP.NET application can read. There are two ways to accomplish this – Apache Reverse Proxying and the Oracle SSO IIS Plug-in.

To preface, Apache Reverse Proxying will route all the calls to your application through Apache first. The other option, the Oracle SSO IIS plug-in is installed on your IIS server, requests are sent directly to the IIS web server, and the requests are intercepted and security is handled by the Oracle plugin.

The Apache reverse proxy will pass all calls sent to a URL in OAS to a site running on another web server.  For example, OAS will take all browser requests to http://oas.client.com/iissite/ and proxy them to http://iis.client.com/.  Responses from IIS will be sent back to OAS and OAS will send the response back to the originating web browser.  In testing this proved to be a little slow, as every single request to the application – images, javascript, css, ajax, postbacks, etc. would all  be sent through the reverse proxy server and require an additional hop for every time.

On top of the performance issues, the ASP.NET application had to run in the root of the IIS web server and use a host header to route requests to the proper site.  ASP.NET uses relative paths (i.e. <script src = ‘/ScriptResource.axd?etc…’/>) for included javascript used in ASP.NET AJAX and ASP.NET validation controls (ScriptResource.axd and WebResource.axd).  Normally this would work fine if the web requests were being sent directly to the IIS server.  However, when behind the reverse proxy server (remember, IIS has no idea it is serving pages behind a reverse proxy) – this causes the web browser to try to retrieve the javascript files from http://oas.client.com/ScriptResource.axd (directly from the OAS web server – where ScriptResource.axd obviously doesn’t exist and will send back a 404 error) instead of properly retrieving the files from http://oas.client.com/iissite/ScriptResource.axd.  This causes all of the ASP.NET AJAX and ASP.NET validation controls to break.

There isn’t any way to get ASP.NET to retrieve those resources from a different path or to somehow prepend a path to the ScriptResource.axd and WebResource.axd URLs.  The ScriptResource.axd issue can be fixed by manually including the individual ASP.NET AJAX javascript files by setting the ScriptPath on your ScriptManager.  This is a pain, but works fine with the Reverse Proxy and the browser will be able to properly retrieve the ASP.NET AJAX files.  Unfortunately, this still leaves the WebResource.axd pointing to the wrong path. WebResource.axd is used to retrieve the javascript used in the client-side validation for ASP.NET Validator controls and without it all client-side validation will be broken.  There isn’t any way that I could find to modify where ASP.NET will retrieve those files.  To get around the .axd issue, we had to get creative and create an IHttpModule that would rewrite the HTML responses and fix the paths on the fly.

Doing a simple find and replace on the .axd paths works fine for regular postback responses to fix the bad paths, but fails with ASP.NET AJAX partial-page updates.   You can find and replace in the partial-page updates, but then it will throw off the field lengths in the pipe-separated data that is sent back to the browser.  Thus, you need to actually find and rewrite the field lengths on the fly as well whenever you do a replace on the .axd paths.  You can see the implementation of this in the ReverseProxyPathFixModule.cs below – it is a little scary, and I’m sure it isn’t full proof because the partial page responses are chunked upon being sent back to the browser.  If there was an .axd path in between chunks, it wouldn’t be replaced – but I never saw this happen.

The most relevant portions of the code below are the Write() methods of PageFilter and PartialPageFilter – they do all the work. The rest of the code is just overridden Stream methods.

View ReverseProxyPathFixModule.cs

After implementing the custom HttpModule, the application was working almost perfectly behind the Reverse Proxy.

For the next hurdle, we couldn’t find any way to have Oracle SSO protect a resource in IIS (or even running in OAS for that matter) while allowing both anonymous and authenticated access.  There isn’t any built-in way to allow anonymous access to an application while it is protected by Oracle SSO.  After much research and reading this Extending Oracle SSO presentation and this Integration with Third-Party Access Management Systems help documentation from Oracle, we decided to create a custom Oracle SSO module that would “authenticate” a user and pass them to the application as the Oracle Portal “PUBLIC” account if they weren’t already logged in to SSO.  The implementation of this plugin is fairly simple – it’s a Java class that inherits from the default Oracle SSO module (SSOServerAuth) and implements the IPASAuthInterface interface.  The code simply checks the user’s cookies on the request – if the user has an Oracle Portal cookie, perform the authentication from the base class  by calling super.authenticate.  If the user doesn’t have a portal cookie, pass them on to the application and “authenticate” them as the PUBLIC user account.  This is definitely a hack, but it works pretty well.  See the implementation of the MixedAuthentication below.

View MixedAuthentication.java

Compilation of the code is a little tricky, you need to include ipastoolkit.jar, ossocls.jar, and servlet.jar in your classpath.  The ossocls.jar isn’t usually included or detailed in the documentation because most Oracle SSO plugins don’t inherit from SSOServerAuth (it isn’t required), but rather just implement IPASAuthInterface.  Deployment is also tricky, fortunately I found this blog post ‘Adding reCAPTCHA to Oracle SSO‘ that detailed how the plugin should be deployed to OC4J_SECURITY container, rather than the standard $ORACLE_HOME/sso/plugins location.

More hurdles! After successfully setting up our custom authentication plugin, we couldn’t figure out how to have our reverse proxy’d application use the custom plugin without it also affecting the client’s Oracle Portal installation.  After we would set the reverse proxy path to use the custom plugin, we would see strange behavior in the Oracle Portal even though portal would be set to use the standard MediumSecurity and our reverse proxy path would be set to use our custom ‘MixedSecurity’ setting.

This is how we tried to set up our Oracle SSO policy.properties file:

#add our custom security level.
MixedSecurity = 70 

#keep the default authentication level so as to not affect oracle portal security.
DefaultAuthLevel = MediumSecurity

#set our custom app behind reverse proxy to use our new custom security level.
oas.client.com/iissite\:80 = MixedSecurity
#not sure if you need the path on OAS or the reverse proxy site. also tried it this way.. didn't work.
iis.client.com\:80 = Mixed Security

#set the plugin class for our custom security level
MixedSecurity = com.client.authentication.MixedAuthentication

No matter what we tried with the SSO configuration we couldn’t get our application behind the reverse proxy to be protected by our custom plugin without also affecting the security of Oracle Portal.  If anyone knows how to actually do that, I’d be interested to hear where we went wrong in the comments.  Unfortunately, this meant that the work with the custom HttpModule, setting up the reverse proxy, etc. was all for naught.  We had to install the Oracle SSO IIS plugin.  This plugin is somewhat of a beast – the installation and configuration is one of the most complicated and least user-friendly  I’ve ever encountered and involves creating registry entries manually, providing many opportunities to make mistakes along the way.

Either way, after installing the IIS plugin everything worked fairly smoothly.  One thing to note – if you want to redirect the user from your ASP.NET application to log in to their actual Oracle SSO account rather than the PUBLIC account, you need to delete the user’s cookie that will look something like IAS_IDXXXXXX – this will “log out” the user from the PUBLIC account.  If the user isn’t logged out of the PUBLIC account before hitting the SSO logon page, they’ll be automatically redirected (to the url provided in the p_requested_url parameter when sending the user to the SSO logon page) when they hit the page because they’re actually already logged in to the PUBLIC account.

One remaining problem, the Oracle SSO IIS plug-in manages to randomly crash the worker process with an error like:

Faulting application w3wp.exe, version 6.0.3790.3959, stamp 45d6968e, faulting module oracle_osso.dll, version 0.0.0.0, stamp 41775fa1, debug? 0, fault address 0×00002454.

Checking the SSO plug-in log files yields nothing out of the ordinary either so this has been pretty difficult to track down, we still haven’t found any solution for this problem.   If anyone knowledgeable on the IIS or Oracle SSO side of things has some ideas or has seen this before, feel free to let me know in the comments.

Finally, after your ASP.NET application is safely behind Oracle SSO you can determine the logged in user’s username by checking the OSSO-USERNAME header like so:

protected override void OnInit(EventArgs e)
{
string username = request.Headers["OSSO-USERNAME"]
//do whatever you like with the username
}

After that, the user’s username from Oracle SSO will come over on the HTTP headers on every request to your application.

Posted By: admin
Last Edit: 04 Nov 2009 @ 09:52 AM

EmailPermalinkComments (3)
Tags
Change Theme...
  • Users » 1
  • Posts/Pages » 10
  • Comments » 13
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

About



    No Child Pages.