Got my nice Urls working

Its amazing how fast things can be done when not fighting bad assumptions. In this case it was that my admin URLs needed to use the nice descriptive URLs, instead of just /[Controller]/[Action]/[id] type routing.

Once I realized I was making things overly complicated I, I ended up with a set of routing functions that are actually a bit less complicated then what I would have come up with if I had been able to extend the RouteTable the way I had planned originally.

I also decided that I didn’t like the idea of having the make every category and example name unique. Most of this was that I’m not the creative, but I also realized that it would be really ugly I wanted to have namespaces as categories. For example, it would not have been possible to have both C/System/Windows/Forms/Control and C/System/Web/UI/Control since Control would have had to be unique.

I also decided that I was going to set a limit to the depth of categories that the site was going to have, though it is easy to update this. First I made a function that would generate routes similar to those previously posted.

protected void CreateRouteSet(string baseRoute, string baseName
    , int maxDepth, object Defaults)
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < maxDepth; i++)
    {
        if (sb.Length > 0)
            sb.Append("/");

        sb.AppendFormat("[{0}{1}]", baseName, i);

        RouteTable.Routes.Add(new Route{
         
            Url = String.Format(baseRoute,sb.ToString()),
            Defaults = Defaults,
            RouteHandler = typeof(MvcRouteHandler)
        });
    }
}

protected void Application_Start(object sender, EventArgs e)
{

    CreateRouteSet("C/{0}", "Category", 6,
        new { Action = "Index", Controller = "Categories" }
        );

    //Other routes using the standard [Controller]/[Action]/[Id]

}

This will setup routes to handle category hierarchies up to 6 deep.  And add Category1,Category2… into the RouteData.Values collection in the controller.

Next I set up the code to allow me to handle the route, which I did in the model, adding a method that takes an IDictionary<string,object>.

public Category GetCategoryFromRouteData(IDictionary<String,object> data)
{
    //Grab just the category data
var navList = from route in data where route.Key.IndexOf("Category") == 0 orderby route.Key select new{Key = route.Key, Value = route.Value.ToString()}; string sqlSelect = @" SELECT {0}.* FROM Category {0} {1} WHERE {0}.Name = {{0}}";
//{{{2}}} will be replaced with {{<param index>}}
string join = @"INNER JOIN Category {0} ON {0}.[Key] = {1}.ParentKey AND {0}.[Name] = {{{2}}} //This is the name of the category I am actually interested in
var mainCat = navList.Last(); //Build a list of category values to for parameterized query
List<string> values = new List<string>(); values.Add(mainCat.Value); StringBuilder sb = new StringBuilder(); string lastCategory = mainCat.Key; int i = 1; foreach (var item in navList.Reverse().Skip(1)) { sb.AppendFormat(join, item.Key, lastCategory,i++); values.Add(item.Value); lastCategory = item.Key; } string sql = String.Format(sqlSelect,mainCat.Key, sb.ToString()); Category cat = this.ExecuteQuery<Category>(sql, values.ToArray()).Single();


//GetDisplayCategory skips over categories that have only 1 subcategory
//and no examples, allowing me to have the planned Url structure without
//
requiring the user to navigate through several empty categories.
return GetDisplayCategory(cat); }

I had planned on having doing it all LINQ to SQL expressions, but it was taking more time then it was worth to figure out how to do the hierarchy lookup from the category names.  I need to add some caching of the key lookups at some point, since the joins look like they might get a bit nasty for deep categories, but that can wait.

I added a few overloads to the HtmlHelper ActionLink extension methods to handle making the nice Url links.

Now I need to figure out how to deploy the usercontrols that will contain the executable part of the example. I have had some problems with compilation sometimes when I had the website directly upload the usercontrols. Plus I would like them to go into source control when I add them.

Technorati Tags:

Initial thoughts on Microsoft MVC

My first thought is, I really wish I could use this at work, but it is working nicely so far for my rewrite of my web site. Writing Classic ASP style again is taking a little getting used to again though, the visual preview from the designers made styling the page a lot nicer.

The lack of control designers also seem like it would hinder having a graphics guy using Expression Web do the UI with MVC based web applications. But I have never worked with a graphics guy on a project that way anyway so I am could be wrong.

 

It would also be nice if the method used by the Route class to parse the Url was virtual, so I could subclass it and allow me to parse something like:

/Example/Simple/Form/MultiPageForm/ShowSource/

to go to the Examples controller, going into the following controller action

[ControllerAction]
public void ShowSource(string[] Categories, string ID)

which would allow me to have a consistent directory scheme for both the ASP.Net examples, along with the Classic ASP examples that are already available.

I could probably do something like /Example/Simple_Form/MultiPageForm/ and use [Controller]/[Category]/[ID]/[Action] but I would prefer it to get to the controller action pre-parsed.

Technorati Tags:

Generic begin/end Html tag extension method

After working on the login usercontrol, I decided that I wanted to add similar functionality to the navigation sections of the site for accounts/roles that didn’t get ads displayed. So I needed a standard way to put dynamic sections into a page.

I liked the syntax used by the form HtmlHelper methods, so decided to base it off of the SimpleForm class and ended up with this

//Based on the SimpleForm class in MvcToolkit 
//Wish I believed I would have ever thought to use
//IDisposable to do this
public class SimpleWrappingTag : IDisposable
{ protected string _beginTagFormat = "<{0} {1}>"; protected string _attributeFormat = "{0}="{1}" "; bool _startTagRendered = false; bool _endTagRendered = false; protected IHttpContext _context; protected object _htmlAttributes = null; //Not letting this be modified outside the class for now //might change later public string TagName { get; protected set; } //Subclassed controls can put their class specific info here public SimpleWrappingTag(string tagName) { this.TagName = tagName; } public SimpleWrappingTag(string tagName, IHttpContext context , object htmlAttributes) { this.TagName = tagName; this._context = context; this._htmlAttributes = htmlAttributes; } /// <summary> /// Creates a StringBuilder and passes it to an overloaded method. /// Override this method for attributes that should be first in tag. /// </summary> /// <returns></returns> protected virtual StringBuilder BuildAttributeList() { return BuildAttributeList(new StringBuilder()); } protected virtual StringBuilder BuildAttributeList(StringBuilder sb) { sb.Append(_htmlAttributes.ToAttributeList()); return sb; } //Method orginally from SimpleForm, but changed to work with //BuildAttributeList instead of BuildFormOpenTag public void WriteStartTag() { if (!this._startTagRendered) { string formTag = String.Format("<{0} {1}>", TagName , BuildAttributeList()); this._context.Response.Write(formTag); } } public void WriteEndTag() { if (!this._endTagRendered) this._context.Response.Write(String.Format("</{0}>", TagName)); } public void Dispose() { WriteEndTag(); } }

Next I made some trivial, but needed extension methods for the HtmlHelper class.

public static class DevExamplesExtensions
{
    public static IDisposable HtmlTag(this HtmlHelper helper, string tagName)
    {
        return helper.HtmlTag(tagName, null);
    }
    public static IDisposable HtmlTag(this HtmlHelper helper
        , string tagName, object htmlAttributes)
    {
        SimpleWrappingTag tag =  new SimpleWrappingTag(tagName
            , helper.ViewContext.HttpContext, htmlAttributes);
        tag.WriteStartTag();
        return tag;
    }
}

And while I’m satisfied with how this turned out, though I’m hoping to be able to delete the code at some point, I don’t like how the code I wrote to implement an Ajax.UpdatePanel method turned out. So I’m not going to post it at the moment.

But it was going to look something like

public static UpdatePanel(this AjaxHelper helper, string panelId, ….)
{
   helper.ViewContext.HttpContext.Write(“update panel js….”);
   IDisposable tag = new HtmlHelper(helper.ViewContext.HttpContext)
        .HtmlTag(“div”,new { id=panelId});

}

Below is a list of things I couldn’t think of a good solution for at the moment

  1. I didn’t like the fact that I was putting the actual JavaScript into the UpdatePanel extension method. Rob Conery said something on the forums about using services to provide the view with site-wide info that seemed interesting, but not sure of a good way to implement this would be.

    It would be nice to have service providers kind of like you can use in Windows Workflow Foundation or VS control designers, that would let me add a IScriptService to do the actual JavaScript coding based on the script library I wanted to use.

    Maybe I should just create a script provider for this.

  2. Having to pass in a list of “triggering” controls felt kind of clunky. I’m trying to think of the best way to allow me to specify that a Html.ActionLink should register itself with the “UpdatePanel”.

    Most of what I think of for this, basically requires me to implement my own versions of the stuff already implemented in the MvcToolkit.

  3. I couldn’t come up with a clean way to have the panel in the Usercontrol, but only have it be done if the usercontrol is called from a page instead of as a standalone view. I guess I could have the controller tell it this, but ideally they would just kind of know.

I think I may have already thought of the solution #3, and a workable #2. But I am really hesitant to do anything with #1 (outside the provider maybe) since I doubt I would do it close enough that I wouldn’t need to remove it all to move over to their way, should they implement something like service providers.

I think I’m going to put this aside for a while on another part of the site, this just is a “nice to have” feature, and get some of the other pieces that I actually need to have for the site done.

 

 

 

* 4 actually, but I said how I wished I could implement an interface using an anonymous type.

Routing revisited

It turned out to be a lot easier to work around not being able to override the route parsing method then I had thought it would be last night. Though when they allow me to override that method I will be changing over.

By setting up the following routes I was able to get almost the effect that I was looking for:

RouteTable.Routes.Add(new Route
{
    Url = "Example/[id]/[action]",
    Defaults = new { Controller = "Example" },
    RouteHandler = typeof(MvcRouteHandler)
});

RouteTable.Routes.Add(new Route
{
    Url = "Example/[Category1]/[id]/[action]",
    Defaults = new { Controller = "Example" },
    RouteHandler = typeof(MvcRouteHandler)
});

RouteTable.Routes.Add(new Route
{
    Url = "Example/[Category1]/[Category2]/[id]/[action]",
    Defaults = new { Controller = "Example" },
    RouteHandler = typeof(MvcRouteHandler)
});

Though the execute action just takes the id in

[ControllerAction]
public void Execute(string id)

 

If I find that I actually need access to the Categories found in the path, I can access them using RouteData.Values. It also helps that I had decided that the examples should have a unique ID, that will be a lot like the friendly Urls used in Subtext. That way the directories are there more for aesthetics then utility. Though when the ability to override the route parsing, I will probably be changing it to this if possible later:

[ControllerAction]
public void Execute(string id, string[] categories)

So now that I have the site navigation working the way that I want it, with the exception of a helper method for writing out the longer category listings, its time to work on the maintenance parts of the site.

And so far it seems that this will be a better fit then WebForms was for the last few reworks of the site, since I can make the UserControls containing the examples completely isolated, including having the runat server form in the control. The only one I liked better was the JSON based one, using ASP.Net AJAX Extensions, which would have gone live if AdSense worked with AJAX/JSON sites.

Overall the more I use it the more I like the feel of the framework. Especially now that I realized which of these  I wanted to selected when using a master page.

And so far the best part of the framework is that I don’t have to ignore features to not see this dialog after refreshing the page I just navigated to.

Technorati Tags:

A few simple MVC control suggestions

I finally got around to updating the website to the MVC preview 2, and ran into a couple of things.

  1. TagBuilder being internal. While the implementation of it CreateInputTag, CreateTag and CreateAttributeList. The removal of .ToAttributeList() was actually what made me go looking for this. The HtmlExtensionUtility extension methods would have been nice to have also.

    I guess a better way to say it is it would be nice not to have to reimplement the functionality that CreateAttributeList and ConvertObjectToAttributeList already implement.

  2. It would be nice if SimpleForm inherited from a class, that could be subclassed to make Div and Span tags that have attributes based on routing information.

    Admittedly it is a rather simple to implement what I am talking about but I think it would be nice to have already there.

 

I’m still rummaging through the routing stuff, so don’t have to much input on those it at the moment. But it looks like I will be able to take all the category discovery information out of my controllers, and just have it passed into the action, which will be nice.

On a side note, after looking at the classes that were made into base classes. I cant really say that I would ever have a need for them to be interfaces. As I can’t think of any other class I would want to tack their functionality onto, after all interfaces were the answer to not having multiple inheritance (I still wish I had it multi-inheritance though).

I do however hope that IRouteHandler stays an interface though, because I can see myself taking that behavior onto an object. Just not sure where.

Technorati Tags:

Anonymous Types and Interfaces

I finally had a chance to sit and work with LINQ for a bit, and while it is very cool, a little bit of its luster was lost when I can assign interfaces to the anonymous classes generated by the queries. Meaning I can not do something like:

public interface IExample
{
    string Name { get; set; }
    string Description { get; set; }
}
public class DataGenerator
{
    public IEnumerable<IExample> GetExamples(string categoryName)
    {
//Context Definition
IEnumerable<IExample> examples = from x in db.Examples
//LINQ to SQL Class
where x.Category.Name = categoryName select new IExamples { Name = x.Name
                                 , Description = x.Description } ; // There is probobly better syntax for this return examples; } }

Now I know I could instead do

public class Example
{
    public string Name { get; set; }
    public string Description { get; set; }
}
public class
DataGenerator
{
    public IEnumerable<Example> GetExamples(string categoryName)
    {
       
//Context Definition
        IEnumerable<Example> examples =
            from x in db.Examples
//LINQ to SQL Class,
            where x.Category.Name = categoryName
            select new Example{ Name = x.Name, Description = x.Description };

        return examples;
    }
}

or if I wanted to continue only exposing an interface

 

public interface IExample
{
    string Name { get; set; }
    string Description { get; set; }
}
internal class Example:IExample
{
    public string Name { get; set; }
    public string Description { get; set; }
}
public class DataGenerator
{
    public IEnumerable<IExample> GetExamples(string categoryName)
    {
        //LINQ to SQL Context Definition
        var examples =
            from x in db.Examples 
where x.Category.Name = categoryName select new Example{ Name = x.Name
                              , Description = x.Description }; return examples.OfType<IExample>(); } }

Over all this isn’t a big issue since the second method works just fine for what I am trying to accomplish for the most part. But it feels kind of off having to make a throwaway class to accomplish this.

Technorati Tags: , ,

LINQ, Anonymous Types and Interfaces Revisited

After making my previous post I worked on it a bit more, and found that the best way to accomplish what I was trying to do, creating quick data layer that I can change later, was to simply add the IExample interface to the partial class definition. I know I can create this with Subsonic or another  DAL generator, but currently they aren’t on the list of tech I want to learn at the moment.

And while this did work, I ran into an error in the dbmI designer when I clicked “View Code” from the context menu:

This seems to happen when the project has a default namespace assigned to it or possibly having an Entity Namespace set, since I have both. I haven’t looked to much into it since VS puts the partial class definition that it goes to in the cs file of the same name as the dbml file, so I doubt I will be using the view code much anyway.

Technorati Tags: , ,

HttpHandlers and directory authentication/HttpModules

I decided to implement  Admin Rss Feeds after a particularly draining Friday. For the most part it went pretty smoothly, and learned something about working with a different team too ;). I Implemented an HttpModule that looked for FormsAuthentication redirects for rss feeds and changed it over to use basic authentication so rss readers could authenticate.

And for a while all was good. In fact other then unit tests I had thought I was done.

Then just to be thorough I set up several subfolder blogs off of localhost, and everything stopped working. Apparently something in the way that the rss HttpHandlers in Subtext are called skips all of the HttpModules set up in the web.config. I have a fix for this, I loop through all of the modules in the application and initialize them. I dont think this is the right solution since it also stops the <authorization> section in the /Admin folder is not being looked at either.

 

Technorati Tags: , ,

Subtext Admin Rss Feeds

I just committed the changes to provide 3 administrative rss feeds:

  • Comments Needing Moderation
  • Referrals
  • Errors

The change also uses the HttpModule that will convert forms authentication into basic authentication so that the feeds can be viewed in an Rss reader. After seeing http://msdn2.microsoft.com/en-us/library/Aa479391.aspx I had thought about changing over to use it instead of the simple module I wrote. I decided not to however because it would have meshed well with Subtexts security model.

This was one of the more interesting things I have worked on in a while, though I am already thinking of several improvements that could be made (in the next version). These include:

  • A module for digest authentication.
  • A Configuration section for the new authentication modules to allow them to work with other file times outside of rss feeds.
  • A rework of the way that rss writers are done. Currently a new one needs to be added, along with a HttpHandler when a feed is going to serve up a new object type. It would be nice to be able to configure the feeds using the web.config or setting stored in database to be able to create feeds on any available subtext object collection.
  • Something seems off with the Error feed’s times. I think the local time is being stored without converting it to UTC or specifying a timezone.

 

Technorati Tags: , ,

HttpHandlers and web.config settings

I figured out what was happening in my previous post. It makes a bit more sense now that I have seen it, being able to just stop working on something is handy, basically the Rss feeds don’t do URL rewriting. So the call to /test1/Admin/ModeratedCommentRss.aspx uses the /web.config and would use the /test1/Admin/web.config, but it has no reason to look at the /Admin/web.config.

Not completely sure how I should change this. Right now I have the ModeratedCommentRss.aspx checking to see if the requestor is an Admin, and if not it calls FormsAuthentication.RedirectToLoginPage(). This works, but I would rather a solution that didn’t involve people needing to know to put the check in.

I also found this module helpful when I was figuring out where to do the conversion:  

public class DebugModule:System.Web.IHttpModule
{
    public EventHandler GetEventhandler(string name)
    {
        return new EventHandler(delegate(object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;
            HttpContext context = app.Context;
            if (context != null)
                Debug.WriteIf(context.Response.StatusCode == 302, "Redirecting - ");
            Debug.WriteLine(name);
        });
    }

    public void Init(HttpApplication app)
    {

        Debug.WriteLine("---------------------------------");
        Debug.WriteLine("Module Init");
        Type appType = app.GetType();
        EventInfo[] events = appType.GetEvents();
        foreach (EventInfo eventInfo in events)
        {
            eventInfo.AddEventHandler(app, GetEventhandler(eventInfo.Name));
        }
    }
}

I used that class and a small test web project to figure out how to change the FormsAuthentication over to Basic authentication (seems like mixed authentication should have already been there though). 

Of course shortly after I figured most of it out I saw the link to the MSDN article Phil Haack had posted for the feature request.