Archive for July, 2010

Would you use an Off-Line Learning Application ?

Over the years I’m done hundreds of videos showing tips, tricks, techniques and methods for building ASP.NET applications.

One drawback of videos, especially as we move toward Hi-Def is that there is still much of the world’s developers lack the bandwidth to view streaming videos.

Some folks have he band width at work but not at home, etc.

So lately I’ve had this application in my head.

It’s a Windows Desktop application (and maybe a Windows Phone 7  application) that starts as large index into Developer content.

The content would be organized into “curriculum based learning” so that it could be referenced piece by piece or by a group as a syllabus.

Off course the index would all be searchable by category, tag, and keyword.

The app could contain and deliver video, audio, code, and written content and be capable of interactive learning modules (finish he module take a quiz, etc.)

The application would manage downloading in “the background” as bandwidth was available. (Either via an http server configured for presumable downloads or via a pre-seeded BitTorrent.

When all the assets for the curriculum are downloaded the user would get a notification that the material is ready for them.

New content would be feed by RSS/RDF and the application would sync an on line profile so that uses could keep track of what materials they have consumed and what they have not.

Anyway, it’s a rough idea and I wanted to ask people what they thought of it?

New Video Series – Learning Microsoft WebMatrix.

Have you heard about ASP.NET WebMatrix ?

There is a new dev tool and installer, a new syntax for developing ASP.NET Web Pages, IIS, SQL Compact.

Here are links to the first two videos in a new series for learning WebMatrix Development.

  1. Getting Started with WebMatrix Beta and ASP.NET Web Pages
  2. Introduction to ASP.NET Web Programming Using the Razor Syntax

 

wm-image_0b134c68-94f5-4abd-89f7-702e3fc0b64c

These videos will linked to a multi-chapter written tutorial series you can find at the link below.

[ Written tutorial series. ]

More coming son !

Verizon sold the territory I live in to Fairpoint. FIOS will never be available where I live (at least not in my life time.)

Comcast has always made be pay for two separate broadband accounts. (2 buildings – pay twice.)

Well. The company she worked for was a Microsoft service provider. When Microsoft suddenly decided not to renew their recurring contract, her employer pretty much closed up shop and she was out of work.

This was a good catalyst to consolidate expenses.

So we’re getting rid of my T-Moblie wireless. My wife’s Verizion wireless, my office Fairpoint land lines, and our home Comcast Phone and internet access.

Step one – 7 Acre Wide Wireless.

I want Hi-Speed wireless access without degradation from the far corner of my office building to across my driveway to the far back corner of the source on both floors.

image

My new setup starts with adding a really good base wireless hub.

wnr3700 

I choose the Netgear Rangemax WNDR3700 Dual Band Wireless-N Gigabit Router

[ Get  One HERE ! ]

At over $150 dollars it’s not the cheapest but it’s worth the extra money.

There were about 200 4/5 star reviews on Amazon.com and about 50 1/2 star reviews.

This is a great case of non technical folks (many of whom THINK they are networking experts) being frustrated and blaming the product for their lack of knowledge.

I unplugged all mu networking devices and started from scratch.

I plugged the WNDR3700 into my Comcast Cable modem and then with a physical cable connected the WNDR3700 to a computer.

The first thing I did was update the Firmware to the latest version.

After rebooting the WNDR3700 I set the SSID Names (one for each channel), Password, and DHCP address range as I desired and rebooted the device again.

Pressed the wireless connect – and BANG!

The WORST signal anywhere in the buildings on my property’s 4 bars.

I  connected a  Cisco / Linksys SR2024 24-port 10/100 Switch that I already had to the Netgear router. (Though I plan to upgare to the gigabit version).

21MQSF236XL__SL500_AA300_

[ Get One Here ! ]

My hard wired connections at peaking at about 30mb down and 3mb up. (Stay tuned – I’m going to improve this soon.)

The WNDR3700 also has a USB NAS feature so I picked up a Seagate Expansion 1.5 TB USB 2.0 Desktop External Hard Drive.

31S1q6M1waL__SS350_

[ Buy One Here ! ]

I plugged this drive in and went into the device options of the WNDR3700’s admin facility to set the network name of the device.

BANG – 1.7 terabytes of NAS easily accessible from all my machines connected to the WNDR3700.

Setup was EASY and everything worked as I expected.

Now for the HARD part.

The router is in my office but I need SOME hard wired connections in the other building (my house).

This means I needed to bridge the wireless signal from my office into a wired hub in my house.

After a good deal of research I selected a Buffalo Technology AirStation Turbo G High Power Wireless Ethernet Converter

4140Q213J2L__SS500_ 

[ Get One Here ! ]

Setting this guy up was a bit more difficult because, even though this device has 4 wired access ports,  I needed to bridge the signal into my home Ethernet wiring system and I wanted to extend it with a combination hub (Wireless / Wired).

Though not really necessary for wireless access since the WNDR3700’s signal is so strong, I decided to add 2 additional wireless/combination routers to my scheme since I already had a couple of good modem routers. (One in the house proper and one in the basement.)

I have two D-Link routers from previous use.

DIR655Wfeyzp3hL__SL500_AA300_

A D-Link DIR-655 Extreme N Gigabit Wireless Router.

[ Get One HERE ! ]

And the slightly older D-Link DIR-625 4-Port RangeBooster Wireless-N Router

DIR6251Omxq2sBL__SL500_AA300_

The secret to adding these 2 routers trough the is on syncing the configuring settings.

The default device IP address is 1.1.1.1

First I changed the device IP address to something gin the same subnet as my WNDR3700 but OUTSIDE the DHCP address allocation pool.

Next I configured each of the D-Link routers with device IP Address and DHCP address allocation pools all in the same subnet (making sure there could be no conflicts).

After wiring everything up – they didn’t work :)

To solve the last problem I needed to synchronize the wireless channels being used.

On ALL THREE wireless routers I turned OFF automatic channel scanning and set to channel to fix on channel 11 (11 is the default on the Buffalo Air Station) .

In the D-Link routers the admin settings look like this.

Channel - 7-27-2010 10-56-08 AM

After setting these options and rebooting all 3 modems in my network (and updating the router firmware) – everything is working FINE !

Awesome wireless and hard wired connections available everywhere.

TODO:

1.) Upgrade my main office switch to GigaBit.

2.) Upgrade my Cable Model (and Comcast Account) to DOCSIS 3

If selected the Motorola SB6120 SURFboard DOCSIS 3.0 eXtreme Broadband Cable Modem

SBEX-q26-01

[ Get One HERE ! ]

Give it all a try :)

Next I’ll tackle phones :)

New Videos and Tutorials for this Week.

I have this GREAT Site Manager that manages the release mechanics of my content on the ASP.NET web site (THNAKS TERRI !)

Here is a list of content that went live this week:

Videos

· ASP.NET MVC For the Rest of Us: Part 4 (Monday, July 19)

· Tailspin Spyworks – Adding User Product Reviews (Wednesday, July 21)

· Tailspin Spyworks – Displaying User Reviews (Wednesday, July 21)

Tutorials (Tailspin Spyworks) – Wednesday, July 21

· Part 1: File-> New Project

· Part 2: Data Access Layer

· Part 3: Layout and Category Menu

· Part 4: Listing Products

· Part 5: Business Logic

· Part 6: ASP.NET Membership

· Part 7: Adding Features

· Part 8: Final Pages, Exception Handling, and Conclusion

There is also a shiny new landing page for Tailspin Spyworks: http://www.asp.net/web-forms/samples/tailspin-spyworks

And, there is a videos RSS feed: http://www.asp.net/rss/videos, and a tutorials RSS feed: http://www.asp.net/rss/tutorials

Have fun !

Technorati Tags: ,WebForms,,,,,

I’m building a drop-in page library for administering ASP.NET Application Services (Membership, Roles, etc) on a public web site.

On the default.aspx page in my Admin folder I wanted a

I didn’t want a top menu, a fancy tree view, etc. using ASP.NET buttons. I’m using buttons because the click event can either be a navigation or an event handler in code behind.

Here is the look

image

In my /Admin folder I “cloned” the default Master.page (now /Admin/Admin.Master).

Next – I created an Admin.css file in the/Admin directory.

Code Snippet
  1. .adminButton
  2. {
  3.     vertical-align: middle;
  4.     text-align:center;
  5.     width: 150px;
  6.     height: 40px;
  7.     border: 1;
  8.     border-color: Silver;
  9.     background-color: #465C71;
  10.     color: White;
  11. }
  12.  
  13. .adminButton:hover
  14. {
  15.     background-color: #BFCBD6;
  16. }

 

In the Admin.Master page I added the Admin.css reference to the <head> element.

Code Snippet
  1. <head id=”Head1″ runat=”server”>
  2.     <title></title>
  3.     <link href=”~/Styles/Site.css” rel=”stylesheet” type=”text/css” />
  4.     <link href=”Admin.css” rel=”stylesheet” type=”text/css” />
  5.     <asp:ContentPlaceHolder ID=”HeadContent” runat=”server”>
  6.     </asp:ContentPlaceHolder>
  7. </head>

Then I put the ASP.NET Buttons on my default page like this.

Code Snippet
  1. <asp:Content ID=”Content2″ ContentPlaceHolderID=”MainContent” runat=”server”>
  2.     <div style=”text-align: center;”><h2>Administration</h2></div><hr /><br />
  3.     <div style=”text-align: center;”>  
  4.         &nbsp;&nbsp;  
  5.         <asp:Button ID=”btnAdminUsers” runat=”server” Text=”List Users” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;  
  6.         <asp:Button ID=”btnAdminActivate” runat=”server” Text=”Inactive Users” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;
  7.         <asp:Button ID=”btnAdminEvents” runat=”server” Text=”Locked Out Users” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;                      
  8.         <br /><br />
  9.         &nbsp;&nbsp;   
  10.         <asp:Button ID=”btnAdminRoles” runat=”server” Text=”Roles” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;
  11.         <asp:Button ID=”btnAdminAccess” runat=”server” Text=”Access Security” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;
  12.         <asp:Button ID=”btnAdminSettings” runat=”server” Text=”Application Settings” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;
  13.         <asp:Button ID=”btnAdminReporting” runat=”server” Text=”Reporting” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;
  14.         <asp:Button ID=”btnAdminMisc” runat=”server” Text=”Misc” PostBackUrl=”" CssClass=”adminButton” />&nbsp;&nbsp;
  15.     </div>
  16. </asp:Content>

Simple !

Hopefully it will save someone some time.

Technorati Tags: ,,

Taking JetBrains Resharper 5 for a spin.

image

I’ve always found Visual Studio to provide me with all that I need for ASP.NET development, so I’ve never really integrated a significant Add-In to my personal software development process.

But, I keep downloading source code projects that contain ReSharper project files, so I thought I’d give it a try and find out why ReSharper is so popular.

The guys at JetBains hooked me up with the latest version and I was up and running in a few minutes.

In a single statement – ReSharper helps you write better code.

I have to confess, it freaked me out a bit when I first installed it and opened a large existing project.

I got lots of code suggestions like this one.

SNAGHTML5089cd5

I worried that ReSharper would only be good if I were starting from scratch.

I quickly discovered that while I’m learning ReSharper, if it gets in the way at all I can just turn it off while I’m working on a “legacy” project.

SNAGHTML503fdc8

Or, ReSharper makes it easy to implement the suggested changes.

image

Of course you can configure EVERYTHING to suit your taste.

image

From the JetBrains site, here are the highlights of the latest version’s updates.

Compared to previous versions, ReSharper 5 has evolved in four major directions:

  1. Web Development. We have greatly extended the toolset available to ASP.NET developers when they work with markup files and web site infrastructure. ASP.NET MVC developers get a bonus in additional code inspections, syntax highlighting, navigation, and code completion for controllers and actions.
  2. Project Maintenance. ReSharper becomes a valuable assistant not only to individual developers but to teams working with large, complicated projects. It helps them view, change and maintain project structure.
  3. Support for Visual Studio 2010. ReSharper 5 provides support for the new Visual Studio version earlier than ever. Of course, Visual Studio 2005 and 2008 are supported, too.
  4. Code Analysis. This area incorporates several improvements, including “plainly” implementing a substantial pack of new code inspections; the opportunity to get an overview of all code smells in solution; upgrading foreach and for loops to LINQ queries; and letting you track how data values and method calls are passed through your code.
  5. Other enhancements include extended IntelliSense, bookmarking, native NUnit support, and more.

 [ Download a free 30 day trail HERE ]

I’ll update you when I have a couple months of use under my belt.

Technorati Tags: ,,ReSharper,

A simple way to Defend Against Unauthenticated Web Service Access.

Scenario – a developer wants to restrict unauthorized access to a we service in his application.

WARNING: This method is easy but not bulletproof !!!!

The level of security desired is modest and the developer does NOT want to use SSL, WS-* extensions or any other certificate based infrastructure.

A customer emailed be this week with this very question.

They were using the ASP.NET Membership Application Service and Forms based authentication in their application and discovered that the user’s authenticated state in the application was not accessible from his web services.

The problem ?

Web Services (ASMX) and Web Pages (ASPX) can both have session state but they do not SHARE session state.

Here are two quick ways to authenticate calls to Web Service Methods. (There are, of course, many ways.)

First…….

You can pass the UserName and Password to the web service method and manually authenticate.

[ Remember, without SSL the username and password are passed over the web in “clear text” and are theoretically exposed to “man in the middle” interceptions. ]

image

The web service method would look like this….

Code Snippet
  1. [WebMethod]
  2. public string CallWithCredentials(string uname, string pwd)
  3. {
  4.     if (Membership.ValidateUser(uname, pwd))
  5.     {
  6.         return “User IS Authenticated.”;
  7.     }
  8.     else
  9.     {
  10.         return “User NOT Authenticated”;
  11.     }
  12. }

 

And be called like this……

Code Snippet
  1. protected void ButtonCallService_Click(object sender, EventArgs e)
  2. {
  3. MembersOnlyWS.MembersOnly ws = new MembersOnlyWS.MembersOnly();
  4. LabelResults.Text = ws.CallWithCredentials(TextBoxUser.Text, TextBoxPwd.Text);
  5. }

Though this works, it is inelegant because it causes the actual credentials to be passed for each web service method call.

Here is a method that I prefer.

I start by adding a table to my ASPNETDB database, though really you could put it anywhere.

image

Next, I add a LoggedIn event handler to the LoginView control in my site’s MasterPage.

Code Snippet
  1. protected void LoginUser_LoggedIn(object sender, EventArgs e)
  2. {
  3.     Guid newTokenId = new Guid();
  4.     using (ASPNETDBEntities db = new ASPNETDBEntities())
  5.     {
  6.         ServiceToken newToken = new ServiceToken();
  7.         newToken.Token = newTokenId;
  8.         db.AddToServiceTokens(newToken);
  9.         db.SaveChanges();
  10.     }
  11.  
  12.     Session["UserServiceToken"] = newTokenId;
  13. }

 

When the user logs in this code does three things.

1.) Gets a new GUID

2.) Stores that GUID in the ServiceTokens table

3.) Stores the same GUID in a session variable.

Now we re-factor our Web Service method to accept a single parameter (a GUID as a sting) instead of a UserName / Password pair.

Code Snippet
  1. [WebMethod]
  2. public string CallWithToken(string token)
  3. {
  4.     if (token == “” || token == null)
  5.     {
  6.         return “User NOT Authenticated”;
  7.     }
  8.     else
  9.     {
  10.  
  11.         using (ASPNETDBEntities db = new ASPNETDBEntities())
  12.         {
  13.             Guid userGuid = new Guid(token);
  14.             var myToken = (from t in db.ServiceTokens where t.Token == userGuid select t).FirstOrDefault();
  15.             if (myToken != null)
  16.             {
  17.                 return “User IS Authenticated.”;
  18.             }
  19.             else
  20.             {
  21.                 return “User NOT Authenticated”;
  22.             }
  23.         }
  24.     }
  25. }

When the method gets called we take the GUID/Token and see if that GUID exists in the ServiceTokens table.

If the web method does not receive a GUID, or if the GUID received does not exist in the ServiceTokens table, the user is declared as “Not Authenticated”.

How YOUR application handles method calls that fail to provide a valid token will be up to you and will depend on the specifics of your application. IN the case of this demo, we just return an “unauthenticated” message.

Now, in our page code we can call our Web Service Method by retrieving the GUID that we previously stored in a session variable and passing it to the web service method.

Code Snippet
  1. protected void ButtonPreAuthCallService_Click(object sender, EventArgs e)
  2. {
  3.     if (!HttpContext.Current.User.Identity.IsAuthenticated)
  4.     {
  5.         Response.Redirect(“~/Account/Login.aspx”);
  6.     }
  7.  
  8.     MembersOnlyWS.MembersOnly ws = new MembersOnlyWS.MembersOnly();
  9.     if (Session["UserServiceToken"] != null)
  10.     {
  11.         LabelPreAuthResult.Text = ws.CallWithToken(Session["UserServiceToken"].ToString());
  12.     }
  13.     else
  14.     {
  15.         Response.Redirect(“~/Account/Login.aspx”);
  16.     }
  17. }

Notice that if the user is not authenticated or the GUID in the session object is empty, the user gets redirected to the LogIn.aspx page.

In practice this means that our user interface won’t let an unauthenticated user make a web service method call but the web service method itself  could be called directly from outside our application and that’s why we require a valid GUID that is created when a user successfully logs in.

There is one gotcha that we need to address.

Every time a user logs in, a GUID gets added to the ServiceTokens table but those tokens never get removed.

The more the application gets used, the higher the likelihood that a brute force attack could stumble on a valid token and those tokens are valid forever (never deleted from the database.).

To solve this problem we’ll need to delete the tokens from the ServiceTokens table when the user is done with them.

If the user logs out this is easy to do.

We can modify the LogedInTemplate of the LoginView control in our master page to hook an event handler for when the user logs out.

Code Snippet
  1. <LoggedInTemplate>
  2.     Welcome <span class=”bold”><asp:LoginName ID=”HeadLoginName” runat=”server” /></span>!
  3.     [ <asp:LoginStatus ID="HeadLoginStatus" runat="server" LogoutAction="Redirect" LogoutText="Log Out" OnLoggedOut="RemoveServiceToken" LogoutPageUrl="~/"/> ]
  4. </LoggedInTemplate>

 

Then we can implement that “OnLoggedOut” event handler as so…..

Code Snippet
  1. protected void RemoveServiceToken(object sender, EventArgs e)
  2. {
  3.     using (ASPNETDBEntities db = new ASPNETDBEntities())
  4.     {
  5.         Guid userGuid = new Guid(Session["UserServiceToken"].ToString());
  6.         var myToken = (from t in db.ServiceTokens where t.Token == userGuid select t).FirstOrDefault();
  7.         if (myToken != null)
  8.         {
  9.             db.DeleteObject(myToken);
  10.             db.SaveChanges();
  11.         }
  12.     }
  13.     Session["UserServiceToken"] = “”;
  14. }

 

Note that the UserServiceToken is removed from the Session Object and the ServiceTokens table in the database.

This is fine if the user actually logs out, but what if the user does NOT log out?

We still want to clean up the GUIDs even if the user never logs out.

[ NOTE : the following method only works if session management is “InProc” if we are using alternate session management then we would need a different Session_End strategy. ]

When the session expires, we can clean up the GUIDs as well.

In the Global.asax.cs file ….

Code Snippet
  1. void Session_End(object sender, EventArgs e)
  2. {
  3.     // Code that runs when a session ends.
  4.     // Note: The Session_End event is raised only when the sessionstate mode
  5.     // is set to InProc in the Web.config file. If session mode is set to StateServer
  6.     // or SQLServer, the event is not raised.
  7.         if (Session["UserServiceToken"] != null)
  8.         {
  9.             using (ASPNETDBEntities db = new ASPNETDBEntities())
  10.             {
  11.                 Guid userGuid = new Guid(Session["UserServiceToken"].ToString());
  12.                 var myToken = (from t in db.ServiceTokens where t.Token ==  userGuid select t).FirstOrDefault();
  13.                 if (myToken != null)
  14.                    {
  15.                    db.DeleteObject(myToken);
  16.                    db.SaveChanges();
  17.                    }           
  18.             }
  19.             Session["UserServiceToken"] = “”;
  20.         }
  21. }

Note that there is some subtle logic here.

When the session expires, the LogInView control will still show the user as logged in but when we actually TEST the authentication state of the user in our code-behind ….

Code Snippet
  1. if (!HttpContext.Current.User.Identity.IsAuthenticated)
  2. {
  3.     Response.Redirect(“~/Account/Login.aspx”);
  4. }

The result will be false and the user will be asked to re-authenticate by entering their username and password in order to call the web service method.

This makes our web service method unlikely to be successfully invoked by an unauthorized user because in order to do so, a bad guy would need to either need to intercept a valid set of credentials or guess a GUID that exists in the ServiceTokens table and use it while a valid user is logged on.

Hope someone finds this useful :)

[ Click HERE to download an ASP.NET 4 sample. ]

Free Tools from Telerik TOMORROW ONLY !

I wanted to let my readers know about a cool giveaway for .NET developers happening tomorrow (July 22, 2010).

Tomorrow only, Telerik will be giving away free licenses for JustCode and JustMock to EVERYONE that attends their  “What’s New” webinar.

Anyone that registers for the free event and joins live at 11:00 AM Eastern tomorrow will get the free licenses. That’s $500 of free software for attending a simple web event.

More details are on Todd’s blog at : http://telerikwatch.com/2010/07/free-justcode-justmock-licenses-for-all.html

The direct registration link for the webinar is: http://bit.ly/deX9uF

ASP.NET Membership – Handling Authenticated Users that are Not Authorized.

image

When you create an ASP.NET Web Forms project using the default template, the web.config file specifies where users will “Log In” to the site.

Code Snippet
  1. <authentication mode=Forms>
  2.   <forms loginUrl=~/Account/Login.aspx timeout=2880 />
  3. </authentication>

The template also provides the “Account” directory as seen in the Visual Studio Explorer snapshot above.

Note that the “Account” contains it’s own web.config file which contains the following elements.

Code Snippet
  1. <?xml version=1.0?>
  2. <configuration>
  3.  
  4.   <location path=Register.aspx>
  5.     <system.web>
  6.       <authorization>
  7.         <allow users=*/>
  8.       </authorization>
  9.     </system.web>
  10.   </location>
  11.  
  12.   <system.web>
  13.     <authorization>
  14.       <deny users=?/>
  15.     </authorization>
  16.   </system.web>
  17.  
  18. </configuration>

The Login.aspx page is accessible to all users because it is specified as the LoginUrl for user authentication.

The web.config file is evaluated top-to-bottom.

The authorization element explicitly denies access to all UNAUTHENTICATED users by way of the specification <deny users=”?” /> where the question mark means all unauthenticated users.

However, if the user does not have an account on the web site, they will need access to the “Register.aspx” page in order to create one.

This resources specific access is provided via the <location> element which explicitly grants access to the “Register.aspx” page with the <allow users=”*”  /> where the star means all users, authenticated or not.

All this means that whenever a resource (page / directory) is requested but the user lack the necessary privileges for that resource, the user is redirected to the loginUrl specified in the application’s default web.config file.

In the default ASP.NET Web Forms template this works fine because the only authorization criteria is Logged In or NOT Logged In.

But what if our Authorization criteria is more diverse than that.

ASP.NET Membership makes it easy to configure URL based Authorization based on “ASP.NET Roles” or even for a specific user.

In the snapshot above showing solution in the Visual Studio Solution Explorer, please note 2 new directories.

  1. Admin
  2. SuperUser

The “Admin” folder will contain pages that implement administrative features for our web site, so we need to restrict access so that a user must be BOTH logged in AND a member of the “Administrator” Role Group in order to access the folders contained pages.

Our ”Admin” folder contains a web.config file with a defaul authorization critera that denies access to all users and then specifically GRANTS access to administrators with the element “<allow roles=”Administrator” />

Note that I have also granted access for ALL USERS to the page “AccessDenied.aspx”

Code Snippet
  1. <?xml version=1.0?>
  2. <configuration>
  3.     <location path=AccessDenied.aspx>
  4.         <system.web>
  5.             <authorization>
  6.                 <allow users=*/>
  7.             </authorization>
  8.         </system.web>
  9.     </location>
  10.   <system.web>
  11.     <authorization>       
  12.        <allow roles=Administrator/>  
  13.        <deny users=*/>      
  14.     </authorization>
  15.   </system.web>
  16. </configuration>

This creates an additional user experience challenge for us since a user can be logged in but NOT an Administrator. By default, when Authorization fails for any resource the user is redirected to the loginUrl.

This will be confusing for the user since they are already logged in, they just aren’t part of the “Administrator” role.

To improve the user experience, we’ll add some code to the Load Event of the Login.aspx page to determine whether or not we got there because an already logged in user tried to access a resource in the “Admin”  folder.

If this is the case we’ll redirect them to the “Admin/AccessDenied.aspx” file which is accessible to all users on the web site.

Code Snippet
  1. using System;
  2. using System.Web;
  3. using System.Web.Security;
  4.  
  5. namespace NETOOPWF.Account
  6. {
  7.     public partial class Login : System.Web.UI.Page
  8.     {
  9.         protected void Page_Load(object sender, EventArgs e)
  10.         {
  11.             string test = System.Configuration.ConfigurationManager.AppSettings["SuperUser"];
  12.             if (Request.IsAuthenticated)
  13.             {
  14.                 string mp = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
  15.                 if (mp != null)
  16.                 {
  17.                     mp = mp.ToLower();
  18.                     if (mp.IndexOf(“%2fadmin”) > -1)
  19.                     {
  20.                         Response.Redirect(“~/Admin/AccessDenied.aspx”);
  21.                     }
  22.                 }
  23.             }
  24.             RegisterHyperLink.NavigateUrl = “Register.aspx?ReturnUrl=” + HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
  25.         }
  26.     }
  27. }

Here’s the Pseudo Code for the logic :

  • Check is the user is already logged in. If Yes (Then we probably got here due to an unauthorized access attempt ) ……
  • Get a string that contains the relative path to the page that was requests.
  • Find out if the requested page is in the ”Admin” directory. If Yes ….
  • Send the user to the “/Admin/AccessDenied.aspx” page.

Now, if the user requests a resourse in the Admin folder and the user is anonymous then they will be presented with the Login Page.

If they are already logged in but NOT an Administrator, they will see this page.

SNAGHTML19704c8

Note that we can use the same process when implementing user specific authorization.

Note also the folder named “SuperUser” in the Solution Explorer

image

The web.config file in the ”SuperUser” folder contains looks like this.

Code Snippet
  1. <?xml version=1.0?>
  2. <configuration>
  3.     <location path=AccessDenied.aspx>
  4.         <system.web>
  5.             <authorization>
  6.                 <allow users=*/>
  7.             </authorization>
  8.         </system.web>
  9.     </location>
  10.   <system.web>
  11.     <authorization>      
  12.        <allow users=SuperUser/> 
  13.        <deny users=*/>     
  14.     </authorization>
  15.   </system.web>
  16. </configuration>

 

Note that instead of allowing users based on a role, as we did in the “Admin” folder, we deny access to everyone and then explicitly GRANT access to ONLY a specific user who’s User Name is “SuperUser”.

We also have an AccessDenied.aspx page in the “SuperUser” folder that is accessible to anyone.

Next we modify the “Login.aspx.cs” file as follows.

Code Snippet
  1. using System;
  2. using System.Web;
  3. using System.Web.Security;
  4.  
  5. namespace NETOOPWF.Account
  6. {
  7.     public partial class Login : System.Web.UI.Page
  8.     {
  9.         protected void Page_Load(object sender, EventArgs e)
  10.         {
  11.             string test = System.Configuration.ConfigurationManager.AppSettings["SuperUser"];
  12.             if (Request.IsAuthenticated)
  13.             {
  14.                 string mp = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
  15.                 if (mp != null)
  16.                 {
  17.                     mp = mp.ToLower();
  18.                     if (mp.IndexOf(“%2fsuperuser”) > -1)
  19.                     {
  20.                         Response.Redirect(“~/SuperUser/AccessDenied.aspx”);
  21.                     }
  22.                     if (mp.IndexOf(“%2fadmin”) > -1)
  23.                     {
  24.                         Response.Redirect(“~/Admin/AccessDenied.aspx”);
  25.                     }
  26.                 }
  27.             }
  28.             RegisterHyperLink.NavigateUrl = “Register.aspx?ReturnUrl=” + HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
  29.         }
  30.     }
  31. }

Since any user who requests a page in the \SuperUser folder and is NOT User Name “SuperUser” will get redirected to the LogIn page we can check to see if the requested resource was in the SuperUser folder.

Since the user named “SuperUser” would have access to all the resources in that folder, we know that the request was directed here because the user was NOT “SuperUser” and therefore is not Authorized.

Then we just redirect the user to the “\SuperUser\AccessDenied.aspx”  page.

SNAGHTML1ad7b9d

Adding ASP.NET Membership to your OWN Database.

ScottGu forwarded me an email from a developer this weekend who wanted to use ASP.NET Membership in an application deployed on a shared hosting account that allows only one SQL Server database.

It’s not all that difficult to add ASP.NET membership (as well as other ASP.NET services) to your existing database.

ASP.NET doesn’t really care where the information repository for it’s built in services live as long as they are complete.

The database that contains the ASP.NET Application Services repository is resolved via a standard connection string in the application’s web.config file.

   1:     <connectionStrings>
   2:      <add name="ApplicationServices"
   3:           connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;                                  

                    AttachDBFilename=|DataDirectory|\aspnetdb.mdf; User Instance=true"
   4:           providerName="System.Data.SqlClient" />
   5:    </connectionStrings>

In the entry above the connection points to the a default database of aspnetdb.mdf in the local application’s App_Data directory.

Note that the default is determined by settings in the machine.config file. If you have run the aspnet_regsql.exe utility on your developer machine to configure a SQL Server database to house the membership / ASP.NET Application Services tables than your machine.config defaults probably point to a SQL Server instance instead of a SQL Express database.

In any event, you can use a connection sting like the one above and simply change the .mdf file name to the one that you need to use in your App_Data folder.

If you are doing this on your shared host that you would specify the connection string to your hosting provider’s database and they would have provided you with the necessary connection information when you signed up for the account.

Once our application can connect to the database we’ve chosen to house out membership information we need to configure our database for use by the Membership Provider.

The ASP.NET installation provides some .SQL script files that we can use to make this process easier.

In Windows Explorer, navigate to your .NET Framework install directory.

On my development machine it was located here.

C:\Windows\Microsoft.NET\Framework\v4.0.30319

Note the highlighted .SQL files in the figure below

ASPSQLServicesCodeFiles

Though all may be useful to you in setting up the various ASP.NET Application Services, for the purposes of this post, we’re only going to set up the ASP.NET Membership service.

We will need the first two .sql files…..

InstallCommon.sql

InstallMembership.sql

Make separate copies of these as we’ll need to edit them before we run them.

Next you need to determine how you will execute T-SQL against your SQL Server instance.

If you are working on your own machine you can simply run SQL Server Management Studio on your machine (or against your SQL Server instance.)

If you’re host does not support direct connections with SQL Server Management Studio then they will likely have provided some web based administration mechanism and you will need to use that to execute the T-SQL.

In my sample application I created an empty SQL Express database named AddedMembershipDemo.mdf

AddedMembershipDemoMDF

First open the copy of InstallCommon.sql  that you made.

You will have to make a couple of edits to the code. You will note several references to the database name “”aspnetdb”……

Here are a few examples:

SET @dbname = N’aspnetdb’

USE [aspnetdb]
GO

You must change all references to “aspnetdb” to the name of the database that you are adding the ASP.NET Application Services to.

Since the database already exists you will want to comment out the code bock that looks like this:

   1: IF (NOT EXISTS (SELECT name

   2:                 FROM master.dbo.sysdatabases

   3:                 WHERE name = @dbname))

   4: BEGIN

   5:   PRINT 'Creating the ' + @dbname + ' database...'

   6:   DECLARE @cmd nvarchar(500)

   7:   SET @cmd = 'CREATE DATABASE [' + @dbname + '] ' + @dboptions

   8:   EXEC(@cmd)

   9: END

  10: GO

Once you have made those changes you should be able to run the resulting script successfully (though you may get a warning about some permission assignments.)

Next open InstallMembership.sql and change the references to aspnetdb to whatever your new database name is and run that script as well.

On success you’re database will contain Tables, Views, Stored Procedures, etc like this.

AddedMembershipDemoMDFTables

If you started your application by creating from the default ASP.NET application template, your web.config file should already have the configuration section for ASP.NET Membership. If not you will need to add it manually as below.

   1: <membership>

   2:   <providers>

   3:     <clear/>

   4:     <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"

   5:          enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"

   6:          maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"

   7:          applicationName="/" />

   8:   </providers>

   9: </membership>

Now you should be able to open your site and start adding users to the membership repository.