Adding the [Authorize] attribute to an #ASPNET #WebForms Page control using custom routing

I am developing a system that uses custom routing to load pages. I needed a quick way to secure the pages so I didn’t have to put the same

If Not Page.User.Identity.IsAuthenticated Then
    Response.Redirect("~/Account/Login?ReturnUrl=" & Server.UrlEncode(Request.Url.ToString())
End If

You get the idea.

Anyway, I remember how beautifully WebAPI and MVC handle this by using the “AuthorizeAttribute” attribute. So, I decided to implement this in my page router.

First, you’ll want to enable custom routing in your application as outlined here. Notice that the article I linked to has a solution already outlined for security. I decided not to go that route as I like this solution better.

I have a custom class for handling the IRouteHandler interface implementation required by the custom routing solution.

Within that handler, I do various things like check the virtual paths to make sure that they are valid. Most custom routes get converted to a custom base page that has a public property on it for use with my system. I instantiate that class, set the custom property, and then return that page as the IHttpHandler required by the GetHttpHandler method of the IRouteHandler interface.

Here is the definition of the route in my RouteConfig.vb file:

        'Content editor route
 routes.Add("Edit Content", New Route _
 ( _
 "EditContent/{contentControl}", New CMSPageRouteHandler("~/Pages/CMSContentEditor.aspx") _
 ))

Here is the class definition of a page with the Authorize attribute on it:

<Authorize(Roles:="Editor")> _
Public Class CMSContentEditor
 Inherits System.Web.UI.Page

...

End Class

Only users in the Editor role will be able to access this page. Let’s see why.

To start off with, we need to see if our page is decorated with the Authorize attribute. I created a function that does this. I pass in the requestContext that was handed off to me in the GetHttpHandler method (make sure you Import the System.Web.Http namespace):

 Protected Function IsPageAuthorized(ByVal page As Page, ByVal requestContext As System.Web.Routing.RequestContext)
        Dim authorized As Boolean = requestContext.HttpContext.User.Identity.IsAuthenticated

        ' Check to see if this page is decorated with the "Authorize" attribute
        Dim authAttribute As AuthorizeAttribute = Nothing

        Dim ptype As Type = page.GetType()
        For Each attr In ptype.GetCustomAttributes(True)
            If TypeOf attr Is AuthorizeAttribute Then
                authAttribute = attr
                Exit For
            End If
        Next


        If authAttribute Is Nothing Then
            ' there was no Authorize attribute on this page, so go ahead and let them in
            authorized = True
        Else
            ' see if this page is authorized
            If Not String.IsNullOrEmpty(authAttribute.Users) Then
                ' reset the authorized variable since we have Users listed
                authorized = False
                If authAttribute.Users.ToLower().Split(New Char() {","}, StringSplitOptions.RemoveEmptyEntries).Contains(requestContext.HttpContext.User.Identity.Name.ToLower()) Then
                    authorized = True
                    GoTo EndOfAuthCheck
                End If
            End If

            If Not String.IsNullOrEmpty(authAttribute.Roles) Then
                ' reset the authorized variable since we have roles
                authorized = False
                ' split the roles into an array
                Dim roles() As String = authAttribute.Roles.Split(New Char() {","}, StringSplitOptions.RemoveEmptyEntries)

                If roles.Count > 0 Then
                    For Each role In roles
                        If requestContext.HttpContext.User.IsInRole(role) Then
                            authorized = True
                            GoTo EndOfAuthCheck
                        End If
                    Next
                End If
            End If
        End If

EndOfAuthCheck:

        Return authorized
    End Function

First, we set the authorized variable to equal whether or not the user is authenticated. This will save us time later when none of the other checks fail.

After we loop through the attributes on the page, the authAttribute will either be null or it will be equal to the instance of the AuthorizeAttribute that is on the page. If we check it and it’s null, then we know the page is authorized, so we go ahead and set the authorized variable to true and continue on. However, if the authAttribute has a value, then we need to check the properties on the attribute to see if the page is authorized.

First, we look to see if the User’s name is included in the array of Users (we get it by splitting the Users property of the attribute by commas). I went ahead and converted the Users property to lower case for comparison sake, just in case.

If we find the User’s name in the Users property, then we simply set the authorized variable to true and continue on. I used a GoTo to short circuit and save the work of the next step: Role checks.

Next, we split the Roles into an array, then loop through each one, seeing if the user is in that role. If so, we set the authorized variable to true and continue on.

Once all of this has taken place, I return the authorized variable to the caller.

In the calling method, here’s what I do:

If IsPageAuthorized(redirectPage, requestContext) Then
    Return redirectPage
Else
    RedirectToLoginPage(requestContext)
End If

Here is the RedirectToLogin method. I add a referrer to the ReturnUrl so I know where the originating request came from. Otherwise, it gets lost when the Login page redirects to the desired page as the Login is now the referrer.

Protected Sub RedirectToLoginPage(ByVal requestContext As System.Web.Routing.RequestContext)
        ' redirect to the login page, but send the current referrer as a QueryString on the return page so it can be accessed
        Dim referrer As String = String.Empty
        If Not requestContext.HttpContext.Request.UrlReferrer Is Nothing Then
            referrer = "/?referrer=" & requestContext.HttpContext.Request.UrlReferrer.AbsolutePath
        End If

        requestContext.HttpContext.Response.Redirect("~/Account/Login?ReturnUrl=" & requestContext.HttpContext.Server.UrlEncode(requestContext.HttpContext.Request.Url.AbsolutePath & referrer))

    End Sub

Then, in my custom page, I simply check the value of the referrer like so:

            If Not Request.UrlReferrer.AbsolutePath.ToLower().Contains("login") Then
                Session(SESSION_KEY_REFERRER) = Request.UrlReferrer.ToString()
            Else
                Session(SESSION_KEY_REFERRER) = Request.QueryString("referrer")
            End If

I can’t use ViewState because of issues I’ve run into with custom routing and ViewState with my pages, so I’ve had to resort to using the Session.

As you can see, adding the Authorize attribute to a custom page is easy when you’re doing custom routing. Of course, if custom routing isn’t for you, then this solution won’t work, but I thought I’d share it for those of you who do use custom routes with dynamic pages.

Is not using #AngularJS career suicide?

Yesterday, I posted an entry on why I’m not using AngularJS in my current project. Bascially, it comes down to client requirements, but some would argue that I could sway my client.  Maybe I could.

That brings up an interesting point. In two years, when this project is completed and I am looking for my next gig, will the fact that I took a job using “old” technology hamper my acquisition of a new position?  Is AngularJS going to be a requirement on my resume in two years or will it be gone by then? I’m sure I’ll be able to find a maintenance position working on older technologies, but what will the “new” thing be in two years?

Are all of these AngularJS projects going to have to be rewritten anyway when the new version of Angular is released? When that happens, will people still choose Angular or will there be better options?

I’m still perfectly comfortable using ASP.NET, but how long will that last?

Since I won’t have Angular on my resume (well, not in any major way), will I be committing career suicide?

Of “old versus new” – Why not #AngularJS? Why #ASPNET?

I recently commented on a friend’s Facebook post about AngularJS stating that I hoped Angular dies a quick and horrible death. I guess that was a bit harsh, but I have to admit that I’m tired of reading about it. To be fair, I have created an Angular app that used routing, controllers, basic authentication, WebAPI, and messaging, so I do have some experience with it. I liked it, it was nice, and the architecture is sound.

I don’t understand why people are abandoning ASP.NET for SPA (Angular in particular). Are postbacks really that bad? The AJAX-style communication with the server is overrated in my book. If you build a good user interface and have a decent connection, then Angular and ASP.NET should take the same amount of time. Front-loading JS libraries or waiting for the server to respond take the same amount of effort. And, you are more kind to a larger install base of browsers. Angular takes a somewhat arrogant approach to browsers in that it only works on the latest. It is unapologetic about this.  I see much the same attitude from developers of Angular apps, as well. They seem to state that “if you don’t use the latest technology to access my app, then I have no time for you. I’ll be damned if I’m going to use that ‘old’ technology”. Not everyone is like this, but I have seen this attitude on some posts.

Simply using a technology because it’s new is not a good enough reason for me. When I developed my Angular JS app, I immediately saw where I was going to have trouble maintaining it in the future. Upgrades would require a complete rewrite and changes to the structure would require the same architectural challenges as any other technology.

I have Dependency Injection in my current application to help with dependencies, but with Entity Framework being the core of my data story, I don’t have a whole lot of trouble getting to what I need.

In the end, I think it comes down to your comfort level with using “old” technology. In my eyes, it’s a sign that it is tried and true. Just because something is new doesn’t mean it is better. However, looking at it from another angle, there are many new features being added to ASP.NET these days that make life a lot easier for the developer and make the application more efficient.

Of course, you’ll probably be reading another AngularJS post in the future where I’ve decided to use it after all and I couldn’t imagine using anything else.  Maybe for my next project. But for now, I must work with WebForms and VB.NET.

Getting #Unity Dependency Injection Working with ASP.NET 4.5.1

There are a lot of articles and blog posts out there about how to get Unity working with ASP.NET. After all of those, I found the best reference for doing this was the MSDN Unity documentation. However, there are a few things that you have to change in order to get it working with ASP.NET 4.5.1.

Here is a page on MSDN that describes what to do:

http://goo.gl/kOxwFo

However, in order to make this work, you will need to change the following lines in Web.config (in the <system.Web> element):

<httpModules>
  <add name="UnityModule" type="Unity.Web.UnityHttpModule, Unity.Web" />
  ... other HTTP modules defined here
</httpModules>

To use the following:

<system.webServer>
    <modules>
        <add name="UnityHttpModule" type="YOUR_NAMESPACE_HERE.UnityHttpModule, YOUR_PROJECT_DLL_HERE" />
    </modules>
</system.webServer>

This will load the correct HttpModule that will perform the dependency injection. This HttpModule is defined on this page:

http://goo.gl/pwXH5E

Don’t forget to follow all of the steps in the above document. This includes a link to the code for extending HttpApplication to allow you to retrieve the container.

http://goo.gl/97E768

I am also using Unity dependency injection with a WebAPI that is also in this project. I am using the same custom resolver with this, so I have created a Module that contains a public method for setting up the dependencies:

Module UnityRegistrationModule
    Public Sub RegisterTypes(ByRef container As IUnityContainer)
        container.RegisterType(Of ITestDtoRepository, TestRepository)(New HierarchicalLifetimeManager())
    End Sub
End Module

I can then call this method from the two places where dependencies need to be set up. That gives me a single place for setting up dependencies. In VB.NET, this is a module, this could be done in C# as a static method on a public Utility class.

Here is my WebAPIConfig that calls this method…

Public Module WebApiConfig
    Public Sub Register(ByVal config As HttpConfiguration)
        ' Web API configuration and services
        ' Set up dependency injection with Unity
        Dim container As New UnityContainer()
        RegisterTypes(container)
        
        config.DependencyResolver = New UnityResolver(container)

        ' Web API routes
        config.MapHttpAttributeRoutes()

        config.Routes.MapHttpRoute(
            name:="DefaultApi",
            routeTemplate:="api/{controller}/{id}",
            defaults:=New With {.id = RouteParameter.Optional}
        )
    End Sub
End Module

I had a little trouble getting all of this to work because my custom HttpModule was not executing. This is because I did not have it configured in the Web.Config correctly. After some messing around with the namespaces and project name, I was finally able to get it working.

Here is some code that uses a dependency from within a web page (the page has a label on it named “lblTest1″):

Public Class _Default
    Inherits Page

    <Dependency()> _
    Public Property _repository As ITestDtoRepository

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        lblTest1.Text = _repository.GetAll(Function(x) x.Id = "5c577a7a-13c4-4391-ab80-8ff567274d2e").SingleOrDefault().Description
    End Sub
End Class

As you can see, once you get the configuration correct, using dependencies from within an ASP.NET page is easy. Now, I can simply add a public property to the page of the desired interface and the DI resolver will inject an instance of it for me at runtime. All I have to do is code to the interface. If the required class changes in the future, all I have to do is associate the new class with the interface in the RegisterTypes() method. This is the beauty of dependency injection. I’m completely sold on this idea.

It took me a while to fully grasp the importance of DI, but now that I have, I can’t imagine life without it.

Using a Generic Func(Of TDto, Boolean) to retrieve data from a custom repository

I am using a custom Data Transfer Object framework in my design. As part of this, I am using the repository pattern to retrieve data from a data store. I will have multiple data stores of varying types, so I can’t just depend on Entity Framework for everything. This will mean a lot of extra work setting up repositories, but it will also give me the added benefit of using Dependency Injection for consuming repositories.

Here is my generic DtoRepository interface that my other repository interfaces will implement:

Public Interface IDtoRepository(Of TDto As DtoBase) : Inherits IDisposable
    Property Session As IUnitOfWork

    Function GetAll() As IList(Of TDto)
    Function GetAll(ByVal predicate As Func(Of TDto, Boolean)) As IList(Of TDto)
    Function Add(ByVal dto As TDto) As String
    Sub Delete(ByVal id As String)
    Sub Update(ByVal dto As TDto)
End Interface

This blog entry will focus on the GetAll(ByVal predicate As Func(of TDto, Boolean)) method and how I use it in my test repository.

Here is the implementation in my test repository:

    Public Function GetAll(predicate As Func(Of Models.TestDto, Boolean)) As IList(Of Models.TestDto) Implements IDtoRepository(Of Models.TestDto).GetAll
        ' create a temporary list to hold any matches
        Dim tList As New List(Of TestDto)

        ' loop through our list of dtos to see if we have a match
        For Each tempDto In _dtoList
            ' try to match the incoming query with the temp dto
            If predicate.Invoke(tempDto) Then
                ' it matched, so add it to our temporary list
                tList.Add(tempDto)
            End If
        Next

        ' the following is a short cut for the List. It is here for demo purposes to show how you can reuse the predicate
        'Return _dtoList.Where(predicate).ToList()

        ' return our temporary list containing all matches
        Return tList
    End Function

_dtoList is a local variable of List(Of TestDto) that contains the data for the test repository.

The code to look at here is the loop that goes through each of the DTOs in the data list and checks to see if we have a match. It does this by invoking the predicate that was passed in. We could have simply passed the predicate into the Where extension method on the List, but I wanted to demonstrate how you can Invoke a generic function to get the desired results for situations where you cannot pass the predicate into the data store. In the case of Entity Framework, I would simply pass this into the Where for my context.

Here is an example of how this method is being used:

   <TestMethod()> _
    Public Sub TestGetAllById()
        Dim testRepository As New TestRepository()

        Assert.AreEqual("Description2", _
                        testRepository.GetAll( _
                            Function(x) x.Id = "9b028687-2fda-4964-af92-836c0a4b13ec") _
                        .SingleOrDefault() _
                        .Description)
    End Sub

As you can see, we use a simple Lambda expression to ask for the DTO with the Id of

"9b028687-2fda-4964-af92-836c0a4b13ec"

Then we make sure that the Description matches what we expected.

Using the generic function in this way allows me to have a more dynamic and generic repository system that I can use with multiple data sources without having to do too much conversion for the ones that support predicates out of the box.

More #VB posts on the way

I recently started a new large project. Because of a client request, it will be developed entirely in VB.NET. So, from the core architecture all the way through the client code itself, everything will be in VB.NET. That means that a lot more posts on my blog will be about VB.NET topics and code samples.

Personally, I’m not all that worried, nor am I the least bit upset by this. My last projects had me maintaining legacy VB code written by someone else. This new project will be all new code written by me, so I’ll be the one to make sure it’s maintainable for the people that will follow behind me doing the maintenance.

I’m pretty excited. VB can do just about everything C# can do (no yield or anonymous methods – and I’ll miss the anonymous methods). I think the future of development is more in the framework changes and not so much in the language of choice. Microsoft is still improving VB.NET, so I don’t think they’ll abandon it any time soon.

So, look for more VB code samples and articles on this blog in the future.

It should still be relevant for .NET developers (including C# developers), as the core ideas and implementation will still be the same, it’s just a different language. I don’t think any C# developer will have trouble porting my VB samples to C# if they need the code.

Calling DataBind() on a GridView within an UpdatePanel to handle a row delete command

I was working on maintaining a site that used a GridView within an UpdatePanel and had difficulties with the DataBind() call. I added a delete button (using a CommandField) to the grid and wired up the command logic to delete from a generic list that I was keeping up with. The grid was bound to the List. It just wasn’t updating the contents of the Grid when I removed records from the source.

It turns out that when I added the Delete command, I was also supposed to handle the OnRowDeleting event. I didn’t receive an error message to let me know this until I removed the UpdatePanel and tried it again. Once I added the handler for the event, it worked just fine. So I added the UpdatePanel back and everything now works as it should.