Tag archive for 'aspnet'

27
Apr

Localizing MVC for ASP.NET views and master pages

Microsoft's MVC for ASP.NET is still under serious development but at the moment support for localization is a little weak. Here's one approach that works with the 04/16 source-drop.

LocalizingWebFormViewLocator class

This class helps by trying to identify language-specific versions of views, user controls and master-pages where they exist, falling back to the generic one where necessary.

public class LocalizingWebFormViewLocator : ViewLocator
{
	public LocalizingWebFormViewLocator() : base()
	{
		ViewLocationFormats = new[] { "~/Views/{1}/{0}.{2}aspx", "~/Views/{1}/{0}.{2}ascx",
			"~/Views/Shared/{0}.{2}aspx", "~/Views/Shared/{0}.{2}ascx" };
		MasterLocationFormats = new[] { "~/Views/{1}/{0}.{2}master", "~/Views/Shared/{0}.{2}master" };
	}

	protected override string GetPath(RequestContext requestContext, string[] locationFormats, string name)
	{
		string foundView = FindViewLocation(locationFormats, requestContext, name, CultureInfo.CurrentUICulture.Name + ".");
		if (String.IsNullOrEmpty(foundView))
			foundView = FindViewLocation(locationFormats, requestContext, name, "");
		return foundView;
	}

	protected string FindViewLocation(string[] locationFormats, RequestContext requestContext, string name, string cultureSuffix)
	{
		string controllerName = requestContext.RouteData.GetRequiredString("controller");
		foreach (string locationFormat in locationFormats) {
			string viewFile = string.Format(CultureInfo.InvariantCulture, locationFormat, name, controllerName, cultureSuffix);
			if (HostingEnvironment.VirtualPathProvider.FileExists(viewFile))
				return viewFile;
		}
		return null;
	}
}

Using the class

To use the class you must set the ViewLocator on the WebFormViewEngine to a new instance of LocalizingWebFormViewLocator (either in the constructor or in your common controller subclass) and ensure that any master pages are specified on the RenderView calls to ensure the localized version is detected.

public class HomeController : Controller
{
	public HomeController() {
		((WebFormViewEngine)ViewEngine).ViewLocator = new LocalizingWebFormViewLocator();
	}

	public ActionResult Index() {
		return RenderView("Index", "Site");
	}

	public ActionResult About() {
		return RenderView("About", "Site");
	}
}

You must also ensure the thread's current UI culture is set. The easiest way to do this is to specify the following in your web.config file's system.web section which will pick it up automatically from the user's browser settings via the HTTP language-accept header.

<globalization responseEncoding="UTF-8" requestEncoding="UTF-8" culture="auto" uiCulture ="auto" />

MVC for ASP.NET default page in pseudo-Japanese via the Babelfish
Then all you need to do is create views and master pages that have the culture name appended between the name and .aspx, e.g:

/Views/Home/Index.aspx (common fall-back for this view)
/Views/Home/Index.ja.aspx (Japanese view)
/Views/Home/Index.en-GB.aspx (British English view)

/Views/Shared/Site.Master (common fall-back for this masterpage)
/Views/Shared/Site.ja.Master (Japanese masterpage)

Caveats

There are some limitations to this solution:

Only primary language is attempted

Only the user's primary language specified in their browser is attempted despite browsers having a complete list in order of preference. Ideally we would scan down this entire list before giving up but that would need more code and there is the issue of whether scanning for several languages across several folders could be too much of a performance hit.

Specifying the masterpage on RenderView

It would be nice if you didn't have to specify the masterpage on renderview but if you do not then the ViewLocator never gets called to resolve the actual masterpage address. This may be for backward compatibility within MVC.

Creating files in Visual Studio

Visual Studio 2008 seems to get a little confused if you create a Index.ja.aspx or Site.ja.aspx - whilst the files are created okay the names are not and you will need to adjust the class names to ensure they don't conflict and make sure the opening declaration on the .aspx file points to the right code-behind page and inherits from the correct name.

Of course the beauty of this approach is you can mix-and-match using dedicated views where required and localising labels in the fall-back view when it isn't.

[)amien

10
Dec

ASP.NET MVC preview available

The first public preview of Microsoft's ASP.NET MVC (model view controller) framework is now available.

Download ASP.NET 3.5 Extensions (EXE) (3.7 MB)

Download MVC Toolkit (ZIP) (400 KB)

The project takes cues from Ruby on Rail's success and looks to address dissatisfaction with the testability and maintainability of WebForms applications and provides an alternative approach that is centered around views, models, controllers with a clear separation of concern and the ability to mock test the individual elements.The official documentation is online and there is a great four-part series over at Scott Guthrie's blog which covers:

Phil Hack and Rob Conery are both now at Microsoft and working on the framework, they have some interesting things to say on it too:

A few other people have already written about the subject too:

Most of the examples and many of the routines/helpers fail to encode output which opens them up to HTML and script injection vulnerabilities. Remember to HttpUtility.HtmlEncode output and use Reflector if you're unsure whether a function is encoding correctly.

The CTP requires Visual Studio 2008 to get the most out if it so either head over to MSDN Subscriber Downloads or grab a 90-day trial edition if you don't already have it installed.

[)amien

28
Oct

Security vulnerabilities are not acceptable in sample code

Earlier this week the ASP.NET article of the day linked to 4-Tier Architecture in ASP.NET with C# which I noticed suffered from both HTML and SQL injection. I promptly informed the author and the ASP.NET site (who pulled the link) but the author was rather unconcerned and wrote (after editing my comment):

Thanks for your feedback Damieng. Sql statement has been used here to make the tutorial simple, it is informed in the DAL description.

The problem is people borrowing this code may not notice the vulnerability or understand how to fix it. This isn't the first time I've seen easily exploited sample code, responded and been buffed off with the it's just sample code excuse.

Writing secure code isn't difficult, time consuming or confusing to read.

Microsoft's forthcoming LINQToSQL and Entity Frameworks provide object-relational mapping that takes care of the SQL, as do other well-known ORM tools such as SubSonic and NHibernate.

If you must write your own data-access-layer (DAL) code use parametrised queries and not string concatenation.

When outputting values be 100% sure whether your technique will encode the values for you or not and be aware of what encoding tools are available to you.

ASP & ASP.NET's Response.Write and <%= %> methods do NOT encode for you and you should be using HttpUtility.HtmlEncode to output data to a HTML stream.

Samples of vulnerable and secure code are in my presentation on Web Security I gave at the Guernsey Software Developer Forum a few months ago.

[)amien

09
Oct

Observations on Microsoft MVC for ASP.NET

Anyone who's tried to develop large complex web sites with ASP.NET has likely run into many problems covering the page and control life cycle, view state and post backs and subsequent maintainability including the difficulty in creating automated unit tests.

Microsoft, taking a cue from the popularity of Ruby on Rails and subsequent .NET related efforts such as MonoRail, are embracing the model-view-controller (MVC) pattern and developing something more suited to web development than WebForms which aimed to make web development as similar to Windows development as possible (despite the major underlying differences in architecture).

The prototype, currently named System.Web.Mvc or Microsoft.Web.Scalene depending on where you look, is headed by Scott Guthrie with both Phil Haack and Scott Hanselman involved (sounds like a dream project and team to be involved with) and a preview release ("drop") is due within the coming weeks.

Gurthrie and Hanselman presented Microsoft MVC at the Alt.Net conference which revealed some interesting details buried in the video, my rough observations and notes based on the prototype they showed follows:

Philosophy

  • Don't repeat yourself
  • Highly extensible & pluggable
  • Use generics to achieve strong-typing without code generation
  • Good performance, fast enough for large-scale sites
  • Separation of concern for maintainability
  • Clean URLs in and out
  • Clean HTML

Extensible

  • Interfaces used extensively
  • No sealed classes
  • Plug-in points for view engines (e.g. MonoRail's NVelocity, Brail)
  • Support for Inversion of Control (IoC) engines (e.g. Windsor, StructureMap, Spring.NET)

Compatibility

  • Runs on the .NET 2.0 CLR
  • Some helper classes require .NET 3.5 (extension methods)
  • Normal Request, Response objects (via interfaces for mocking)
  • Does not support postback (form runat="server")
  • Supports MasterPages, DataBinding, CodeBehind
  • Existing .aspx's are blocked using web.config

Visual Studio

  • Solution templates for web project and unit testing
  • Full designer & IntelliSense integration

Flow

  • Route -> ControllerFactory -> Controller -> Action -> ViewEngine -> View

Routing

  • Routes can be defined dynamically and support RegEx URL matching
  • IControllerFactory pops out the required IController
  • Routing is case insensitive by default
  • Support REST, blog engine, Jango style mappings etc. default is /controller/action/parameters

Controllers

  • FrontController style
  • IController exposes:
    • Execute(IHttpContext context, RouteData routeData)
    • IViewEngine ViewEngine property
  • Some implementations available, all with virtual methods:
    • ControllerBase (adds dispatching)
    • Controller (Populate parameters into ViewData["key"])
    • Controller<T> (Populates parameters by making ViewData type T)
  • Attributes
    • [ControllerAction] attribute to expose methods as actions (secure default behaviour by not exposing helper methods)
    • Attribute for output caching
    • [ControllerAction(DefaultAction=true)] to override default method of Index

Parameters (to the controller)

  • Automatically parsed where a TypeConverter exists
  • Future versions will support more complex serialization of types
  • Can be nullable - use null coalesce operator for defaults

View Engine

  • IViewEngine
    • IView LoadView(string viewName)
  • Implementations include:
    • WebFormViewEngine

Views

  • IView
    • virtual void RenderView(object data)
  • Implementations include:
    • ViewPage - pick up parameters from ViewData[""] in conjunction with Controller
    • ViewPage<T> pick up parameters from ViewData as type T in conjunction with Controller<T>

HTML

  • Clean HTML generation (have they sorted out the id mangling by MasterPages/INamingContainer?)
  • Static Html class supports Link, PagingLinks and Url methods
  • Map to action names using Lambda expressions to ensure follows refactoring, e.g.
    string url - Html.Link<ProductController>(controller =>controller.Edit(4);
  • Is there a way to follow the default action?

Model/data

  • Pagination extension methods extend IQueryable for getting pages of data (skip, limit)
  • Pattern for view-update-view cycle
  • Object property to form field id mapping available and pluggable, allows
    • product.UpdateFrom(Request.Form)

Testing

  • Easy to write tests by using mock objects (request, response)
  • Unit testing framework project (NUnit, MBUnit, xUnit.NET)

Finally

  • What's with ScottGu's nametag, can the show's organisers not afford anything more than a PostIt note?
  • What cool software is Scott Hanselman using to do the screencast video/zoom/overlay/highlighting?

[)amien

19
Sep

Castle Project & MonoRail concerns

I really want to like MonoRail but I find it difficult to feel the love no matter how much I desire Rails-like features without the scalability issues and unusual syntax/learning curve of Ruby.

Here's my list of concerns:

Slow release cycle

The current release is 1.0 RC2 which was released November 1, 2006. That's some 10 months ago and yet we have no RC3 or final release.

Part of the hold-up seems to be trying work out what to include and exclude, which brings me onto my next point...

Abandoning convention over configuration?

One guiding principle behind Ruby on Rails is Convention over Configuration. This means that instead of allowing you to configure everything to the nth degree a single 'conventional' approach is taken..

This results in an gentler learning curve with less to configure and is generally part of KISS.

The Castle Project's take on this philosophy is a little off by providing you with a combination of view engines and IoC interfaces to chose from.

In the case of view engines neither NVelocity or Brail support C# or VB.NET which does mean you need to learn another language for writing your views and loose syntax highlighting and IntelliSense in the process.

If I was prepared to accept all that I might as well switch to Ruby on Rails.

The WebForm view engine supports C# but has a number of limitations and uses what I was trying to get away from in the first place (WebForms).

Ken Egozi has come up with a C# based view engine that avoids WebForms confusingly named AspView.

With Inversion of Control whether you use the MicroKernel directly or the Windsor Container wrapper is up to you and I hope you figure out which to use better than I did.

Castle Project web site

Looks good however needs better maintenance/management:

  • Wiki contains spam (FAQ, Tutorial, Tips and Tricks etc.)
  • Samples download returns a 404 not found
  • Enabling Inversion of Control is an empty stub yet is linked from the main intro page

I couldn't find anything about how navigation/site maps are managed.

Going forward

Castle Project say their projects are independent and they should consider breaking them into separate downloads with individual release cycles to keep the pieces moving independently and competitively.

MVC alternatives

Microsoft are working on an MVC engine for ASP.NET themselves. Whilst Microsoft's track record with user interface tool kits is less than spectacular the recent work with LINQ and knowing that both Scott Guthrie and now Phil Haack are on the team inspires more confidence.

Until they release a CTP and a timetable however this can't be seriously considered especially for projects that need to start soon. I'm hoping we'll see a similar cycle to the AJAX and Control Adapters where early access & final versions were available that hooked in to currently shipping versions of .NET and Visual Studio so that we're not waiting for .NET 4.0.

RC3 was released the following day. Check out the contents or download it.

[)amien

03
Aug

Typed session data in ASP.NET made easier still

Philippe Leybaert is unimpressed with Microsoft's Web Client Software Factory approach for typed session data and offers his own Typed session data made (very) easy which still seems overkill to me comprising as it does of generics, a delegate a helper class to achieve the desired effect. (Whilst you are there check out his very interesting MVC project for ASP.NET called ProMesh)

The solution which I have been using since my .NET 1.1 days is much simpler still and involves nothing more than creating a plain class with properties for every session variable and a static get accessor that obtains or creates it on the HttpContext similar to a singleton.

Here's an example with the important Current property (slightly cleaned up and improved for this post ;-)

public class MySession {
    public string Name;
    public int LoginID;
    public int CustomerID;

    public static MySession Current {
        get {
            MySession currentSession = HttpContext.Current.Session["_session"] as MySession;
            if (currentSession == null) {
                currentSession = new MySession();
                HttpContext.Current.Session["_session"] = currentSession;
            }
            return currentSession;
        }
    }
}

Using the session data then simply involves operations like:

MySession.Current.Name = NameTextBox.Text;
NameLabel.Text = MySession.Current.Name;

This solution is a lot clearer however all of these solutions use HttpContext.Session which is actually supposed to be there for compatibility with ASP.

Ideally Microsoft would provide us with an option in web.config whereby we can choose our session class and it would just instantiate and track it as part of the session life-cycle.

[)amien

26
Jul

Rails-style controllers for ASP.NET

Rob Conery has been putting together some great screen casts on SubSonic and his latest on generating controllers pointed out that ASP.NET doesn't support the Rails-style http://site//controller/method style of execution.

This got me quite excited and I've put together a proof-of-concept web project that demonstrates mapping the path to controller methods using a IHttpHandler and reflection.

How it works

It registers the ControllerHttpHandler via the web.config:

<httpHandlers>
    <add path="/ctl/*/*" verb="POST,GET,HEAD" type="ControllerHttpHandler" />
</httpHandlers>

There is a very basic Controller abstract base class that just provides a simple way of accessing the context for dealing with request/response for now.

public abstract class Controller
{
    protected System.Web.HttpContext context;

    internal Controller(System.Web.HttpContext context) {
        this.context = context;
    }
}

We then have a test controller or two that implement from this with a couple of methods and the required constructor:

public class TestController : Controller
{
    public TestController(System.Web.HttpContext context)
      : base(context) { }

    public void Index() {
        context.Response.Write("This is the index");
    }

    public void Welcome() {
        context.Response.Write("Welcome to the TestController");
    }
}

Finally the magic that joins them up is the ControllerHttpHandler:

using System;
using System.Web;
using System.Reflection;

public class ControllerHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context) {
        string[] parts = context.Request.Path.Split('/');
        if (parts.Length < 4) {
            context.Response.Write("No controller & member specified");
            return;
        }

        string controllerName = parts[2];
        string methodName = parts[3];
        Type potentialController = Type.GetType(controllerName);
        if (potentialController != null && potentialController.IsClass && potentialController.IsSubclassOf(typeof(Controller))) {
            MethodInfo potentialMethod = potentialController.GetMethod(methodName);
            if (potentialMethod != null) {
                Controller controller = (Controller) Activator.CreateInstance(potentialController, context);
                potentialMethod.Invoke(controller, null);
            }
            else
                context.Response.Write(String.Format("Method '{0}' not found on controller '{1}'", methodName, controllerName));
        }
        else
            context.Response.Write(String.Format("Controller '{0}' not found", controllerName));
    }

    public bool IsReusable {
        get { return false; }
    }
}

That's it!

Limitations

The controllers and methods are mapped at run-time using reflection. This would probably be too slow for production. Also it currently has to be in a top-level folder because I can't figure out how to pass the Http request back to ASP.Net to try with the rest of the stack if we don't have a matching controller/method.

One option might be to have no httpHandlers in the web.config and add the exact controller/method maps at build or runtime. This solves both the top-level problem and potentially the speed.

Another option to address just the speed of reflection would be to cache the path/method strings to the actual method and type so the only reflection would be the Activator.CreateInstance. If that is slow then we could look at pooling the controller instances themselves.

Going forward

Parameters for a method could be extracted and parsed from the query-string - they are currently ignored.

Response is raw output - we could do something very similar to rhtml.

I'm going to chat things over with the Subsonic team and see if we can come up with anything from here.

[)amien

25
Sep

Extending GridView to access generated columns

ASP.NET's GridView is a useful control and one of it's best features is it's ability to generate the columns automatically from the data source given.

The problem however is that these generated columns are not exposed as part of the Columns collection or indeed available at all so you can't hide or manipulate the selected columns.

One simple scenario might be that you want the first column to be a "View" link to drill down into the row displayed. Whilst you can add the column to the GridView before data binding you can't actually pull out the information needed from another columns to construct the URL.

By sub classing GridView you can obtain this functionality with some caveats.

Version 1: Auto generated columns added to the Columns collection... with caveats.

using System;
using System.Data;
using System.Collections;
using System.Web.UI.WebControls;

public class GridViewEx1 : GridView
{
    private DataControlFieldCollection originalColumns;

    public GridViewEx() : base() {
    }
    public void RecordColumns() {
        originalColumns = new DataControlFieldCollection();
        foreach(DataControlField column in Columns)
            originalColumns.Add(column as DataControlField);
    }

    public void ResetColumns() {
        if (originalColumns == null)
            RecordColumns();
        else {
            Columns.Clear();
            foreach(DataControlField column in originalColumns)
                Columns.Add(column as DataControlField);
        }
    }

    protected override ICollection CreateColumns(PagedDataSource dataSource, bool useDataSource) {
        ResetColumns();
        ICollection generatedColumns = base.CreateColumns(dataSource, useDataSource);
        foreach(DataControlField column in generatedColumns)
            if (!originalColumns.Contains(column))
                Columns.Add(column as DataControlField);
        return Columns;
    }
}

This version provides some compatibility with existing code/expectations in that the autogenerated columns are part of the Columns collection after the DataBind.

Should you call DataBind again however as well as wiping out the changes to the generated columns (they are, after all re-generated) any additional columns added to the collection after the first DataBind will also be lost as it does not track which are added by the programmer and which automatically.

Version 2: All bound columns exposed as BoundColumns, user ones as Columns.

using System;
using System.Data;
using System.Collections;
using System.Web.UI.WebControls;

public class GridViewEx2 : GridView
{
    private DataControlFieldCollection boundColumns = new DataControlFieldCollection();

    public GridView() : base() {
    }

    public DataControlFieldCollection BoundColumns {
        get { return boundColumns; }
    }

    protected override ICollection CreateColumns(PagedDataSource dataSource, bool useDataSource) {
        ICollection generatedColumns = base.CreateColumns(dataSource, useDataSource);
        BoundColumns.Clear();
        foreach (DataControlField column in generatedColumns)
            BoundColumns.Add(column as DataControlField);
        return BoundColumns;
    }
}

After the DataBind you will have full access to the generated columns as part of the BoundColumns collection.

[)amien




Feed subscription

Subjects