A quick observation...

Startable facility and hosting base4

Ivan's pretty keen :) last week he demonstrated hosting the base4 service in ASP.Net ... cool stuff.



Now here he has placed the implementation in the HttpApplication class, and as a quick observation this would be a good candidate for moving into it's own component... and as luck would have it, Castle has just the thing, via the Startable facility.

Startable... (and the implied, stoppable ;o)

Now the startable facility is a very simple beast, basically... you create a class, that implements this interface:


///
/// Interface for components that wish to be started by the container
///

public interface IStartable
{
void Start();
void Stop();
}


Which lives in the Castle.Core assembly... though the facility itself lives in the Castle.MicroKernel assembly... you should already have references to both, otherwise I doubt your container is working at all :P



And you then register the startable facility in the container:



type="Castle.Facilities.Startable.StartableFacility, Castle.MicroKernel" />


And thats it... now whenever you register a component in the container, the startable facility will check it, and if it's found to implement IStartable it will add it to a list of components "awaiting startup"... once any of these awaiting components has had all it's dependencies satisfied it will be started (so it may be started as soon as the component is registered, if it doesn't have any dependencies - this will be the case for our component)...



Disposing of the container will cause the components to be stopped in turn... perfect for hosting our base4 server...

Reworking the hosting code to be a startable component...

...so taking what Ivan's done, we could rework it a little... and be able to host base4 in web apps, as well as say your business logic's test harness... here's my quick 'n dirty reworked implementation as a component that I did this afternoon... my final version probably won't have any dependencies on the HttpContext as I'll just rely on explicit declaration of all the settings as parameters in the container configuration... I tend to like keeping all the config in the container, one place to look 'n all that.


public class Base4Host : IStartable
{
private const string _machineName = "localhost";
private const string _provider = "SQL2005";
private string _appName;
private int _port;
private string _root;
private ServerConfiguration _configuration;
private IServerProxy _proxy;

public ServerConfiguration Configuration
{
get { return _configuration; }
}

public IServerProxy ServerProxy
{
get { return _proxy; }
}

public Base4Host(string appName, int port)
{
if (string.IsNullOrEmpty(appName)) throw new ArgumentNullException("appName");
if (port <= 1024)="" throw="" new="" argumentoutofrangeexception("port",="" "port="" should="" be="" greater="" then="">
_appName = appName;
_port = port;
}

public Base4Host(string appName, int port, string root)
: this(appName, port)
{
if (string.IsNullOrEmpty(root)) throw new ArgumentNullException("root");
_root = root;
}

#region IStartable Members

public void Start()
{
_configuration = CreateConfiguration();
_proxy = ServerFactory.StartServer(_configuration, false);
string base4Context = string.Format("tcp://Server:@localhost:{0}/{1}", _port, _appName);
StorageContext.SetDefault(base4Context);
}

public void Stop()
{
_proxy.Stop();
_proxy = null;
_configuration = null;
}

#endregion

#region Support methods

private ServerConfiguration CreateConfiguration()
{
ServerConfiguration configuration = new ServerConfiguration();

DiscoverApplicationName();
InsertApplicationRoot(configuration);
InsertConnectionStrings(configuration);
InsertConnectivityInformation(configuration);

if (!Directory.Exists(configuration.Store.Root))
{
Directory.CreateDirectory(configuration.Store.Root);
}

return configuration;
}

private void InsertConnectivityInformation(ServerConfiguration configuration)
{
configuration.Store.Name = _appName;
configuration.Store.Provider = _provider;
configuration.Store.Port = _port;
configuration.Store.MachineName = _machineName;
}

private void DiscoverApplicationName()
{
if (string.IsNullOrEmpty(_appName))
{
if (HttpContext.Current.Application["AppName"] == null)
{
Assembly assembly = Assembly.GetCallingAssembly();
HttpContext.Current.Application["AppName"] = (AssemblyTitleAttribute.GetCustomAttribute(assembly, typeof(AssemblyTitleAttribute)) as AssemblyTitleAttribute).Title;
}

_appName = HttpContext.Current.Application["AppName"].ToString();
}
}

private void InsertApplicationRoot(ServerConfiguration configuration)
{
string root = string.IsNullOrEmpty(_root) ? HttpContext.Current.Server.MapPath("~/") : _root;
configuration.Store.Root = root.EndsWith("") ? root + "App_DataBase4" : root + "App_DataBase4";
}

private void InsertConnectionStrings(ServerConfiguration configuration)
{
string connectionStringName = ConfigurationManager.AppSettings["DefaultConnection"];

if (string.IsNullOrEmpty(connectionStringName) && ConfigurationManager.ConnectionStrings != null
&& ConfigurationManager.ConnectionStrings.Count > 0)
{
connectionStringName = ConfigurationManager.ConnectionStrings[0].Name;
}

configuration.Store.ConnectionString = string.IsNullOrEmpty(connectionStringName) ? ConfigurationManager.AppSettings["Store.ConnectionString"] :
ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;

SqlConnectionStringBuilder connStrBuilder = new SqlConnectionStringBuilder(configuration.Store.ConnectionString);

connStrBuilder.InitialCatalog = "master";
configuration.MasterConnectionString = connStrBuilder.ToString();
}

#endregion
}



Notice we don't expose a default constructor... how can the container create it?



Well, it can't unless we supply the parameters it requires... which is just what we'll do:


<>
id="base4host.default"
type="MyProject.Base4Host, MyProject"> GoatsAndBoats 11888




Simple... a couple of useful extensions to our existing base4 facility may well be to:
  • Ensure the startable facility is installed... so we can fail-early and with a meaningful error during application
    startup.
  • Add some additional "optional" parameters for configuring the facility, so that it can take care of registering the Base4Host for us.

For the optional parameters, I'm thinking of doing something like this:


type="SomeProject.Core.Base4StorageFacility, SomeProject.Core"
host="true"
appName="GoatsAndBoats"
port="11888"
connectionString="local"
/>


At which point it's trivial to shift between accessing base4.net as a remote service vs. hosting it in your own app... unlike the remote setup you can skip providing the base4 url (context string) as it'll be implied from the app name and port.
Read More

Looking into Trac

Trac

I know it's not .Net, but that's not always a bad thing...

Well I tend to keep my eye out for things which take my fancy, or more importantly, free things that take my fancy...



Trac is one of those things, a project management and bug database web app written in Python, written by edgewall software... it's actually pretty cool... for RoR enthusiasts they're probably aware of it's existence already (considering RoR use this for manging their bugs and patches)



First off, you can run it on windows...  though a *nix is generally better, and it's not too hard to get going - took me about half an hour... second thing, it's a lot better once you install a few hacks especially:
Now, at this point what makes it good?
  • SVN Integration
  • Wiki Integration
  • Extensibility and community support

Basically I can check in code with comments containing wiki markup and references to existing "tickets" logged in the system... The wiki integration incidentally is my primary motivation for looking so closely at using this for some of the projects I've been working on... I'm a liability to Seismic Technologies... documenting build processes etc. in a wiki are one way we can minimize that... We already have a customer-focused wiki, but it doesn't encourage maintenance on a day-to-day basis like this product does.



secondary to that is the very good subversion integration, a must considering that's the only source control system I use.

Creating tickets via XML-RPC in C#

Now the last hack I suggested exposes plenty of functionality in the system via XML RPC... at which point we can start logging bugs remotely, from C#... to do this we'll need a copy of the xmlrpc.net library



I'm not conducting a tutorial here ;o) but if Trac takes your fancy you might find this code handy... first off this is our interface for logging tickets in Trac - incidentally this is only a small subset of the funcionality exposed.


[XmlRpcUrl("http://localhost/trac/login/xmlrpc")]
public interface ITicket : IXmlRpcProxy
{
[XmlRpcMethod("ticket.query")]
int[] Query(string qstr);

[XmlRpcMethod("ticket.getRecentChanges")]
int[] GetRecentChanges(DateTime since);

[XmlRpcMethod("ticket.getAvailableActions")]
string[] GetAvailableActions(int id);

[XmlRpcMethod("ticket.getTicketFields")]
TicketField[] GetTicketFields();

[XmlRpcMethod("ticket.create")]
int Create(string summary, string description, XmlRpcStruct attributes);

[XmlRpcMethod("ticket.get")]
object[] GetTicket(int id);

[XmlRpcMethod("ticket.delete")]
void Delete(int id);

[XmlRpcMethod("ticket.update")]
object[] Update(int id, string comment, XmlRpcStruct attributes);

[XmlRpcMethod("ticket.type.getAll")]
string[] GetAllTypes();

[XmlRpcMethod("ticket.resolution.getAll")]
string[] GetAllResolutions();

[XmlRpcMethod("ticket.priority.getAll")]
string[] GetAllPriorities();

[XmlRpcMethod("ticket.component.getAll")]
string[] GetAllComponents();

[XmlRpcMethod("ticket.version.getAll")]
string[] GetAllVersions();

[XmlRpcMethod("ticket.severity.getAll")]
string[] GetAllSeverities();

[XmlRpcMethod("ticket.milestone.getAll")]
string[] GetAllMilestones();
}



Now, tickets have a bunch of attributes associated with them...


public static class TicketAttributes
{
public const string Cc = "cc";
public const string Keywords = "keywords";
public const string Status = "status";
public const string Type = "type";
public const string Owner = "owner";
public const string Version = "version";
public const string Resolution = "resolution";
public const string Reporter = "reporter";
public const string Milestone = "milestone";
public const string Component = "component";
public const string Summary = "summary";
public const string Description = "description";
public const string Priority = "priority";
}


And we can make ticket information easier to get a hold of with a simple class:


///
/// represents the information for a ticket
///

public class TicketInfo
{
private int _ticketId;
private DateTime _created;
private DateTime _lastModified;
private XmlRpcStruct _attributes;

public TicketInfo()
{
}

public TicketInfo(object[] values)
{
Update(values);
}

internal void Update(object[] values)
{
if (values == null) throw new ArgumentNullException("values");
if (values.Length != 4) throw new ArgumentException("values should have 4 elements");

_ticketId = (int)values[0];
_created = DateHelper.ParseUnixTimestamp((int)values[1]);
_lastModified = DateHelper.ParseUnixTimestamp((int)values[2]);
_attributes = (XmlRpcStruct)values[3];
}

///

/// The identifier for this ticket
///

public int TicketId
{
get { return _ticketId; }
set { _ticketId = value; }
}

///

/// date and time the ticket was created
///

public DateTime Created
{
get { return _created; }
set { _created = value; }
}

///

/// date and time the ticket was last modified
///

public DateTime LastModified
{
get { return _lastModified; }
set { _lastModified = value; }
}

///

/// The attributes for this ticket, this will include any additional fields
/// that aren't defined explicitly as members of this class.
///

public XmlRpcStruct Attributes
{
get
{
if (_attributes == null) _attributes = new XmlRpcStruct();
return _attributes;
}
set { _attributes = value; }
}

public string Cc
{
get { return GetAttribute(TicketAttributes.Cc); }
set { SetAttribute(TicketAttributes.Cc, value); }
}

public string Keywords
{
get { return GetAttribute(TicketAttributes.Keywords); }
set { SetAttribute(TicketAttributes.Keywords, value); }
}

public string Status
{
get { return GetAttribute(TicketAttributes.Status); }
set { SetAttribute(TicketAttributes.Status, value); }
}

public string Type
{
get { return GetAttribute(TicketAttributes.Type); }
set { SetAttribute(TicketAttributes.Type, value); }
}

public string Owner
{
get { return GetAttribute(TicketAttributes.Owner); }
set { SetAttribute(TicketAttributes.Owner, value); }
}

public string Version
{
get { return GetAttribute(TicketAttributes.Version); }
set { SetAttribute(TicketAttributes.Version, value); }
}

public string Resolution
{
get { return GetAttribute(TicketAttributes.Resolution); }
set { SetAttribute(TicketAttributes.Resolution, value); }
}

public string Reporter
{
get { return GetAttribute(TicketAttributes.Reporter); }
set { SetAttribute(TicketAttributes.Reporter, value); }
}

public string Milestone
{
get { return GetAttribute(TicketAttributes.Milestone); }
set { SetAttribute(TicketAttributes.Milestone, value); }
}

public string Component
{
get { return GetAttribute(TicketAttributes.Component); }
set { SetAttribute(TicketAttributes.Component, value); }
}

public string Summary
{
get { return GetAttribute(TicketAttributes.Summary); }
set { SetAttribute(TicketAttributes.Summary, value); }
}

public string Description
{
get { return GetAttribute(TicketAttributes.Description); }
set { SetAttribute(TicketAttributes.Description, value); }
}

public string Priority
{
get { return GetAttribute(TicketAttributes.Priority); }
set { SetAttribute(TicketAttributes.Priority, value); }
}

#region Support methods

private string GetAttribute(string name)
{
if (Attributes.Contains(name))
{
return Convert.ToString(Attributes[name]);
}
return null;
}

private void SetAttribute(string name, string value)
{
if (Attributes.Contains(name))
{
Attributes[name] = value;
}
else
{
Attributes.Add(name, value);
}
}

#endregion
}



And last of all we have a class for managing tickets.. by the way this isn't complete, I haven't finished writing it because this is just one of my back burner projects...


public class TicketManager
{
private ITicket _ticket;

public void Connect(string url, string userName, string password)
{
_ticket = XmlRpcProxyGen.Create();
_ticket.Url = url;
_ticket.PreAuthenticate = true;
_ticket.Credentials = new NetworkCredential(userName, password);
}

public string[] GetAvailableActions(int id)
{
return _ticket.GetAvailableActions(id);
}

public string[] GetAvailableActions(TicketInfo ticket)
{
ValidateTicket(ticket);
return _ticket.GetAvailableActions(ticket.TicketId);
}

public int[] GetRecentChanges(DateTime since)
{
return _ticket.GetRecentChanges(since);
}

public void DeleteTicket(int ticketId)
{
_ticket.Delete(ticketId);
}

public void DeleteTicket(TicketInfo ticket)
{
ValidateTicket(ticket);
DeleteTicket(ticket.TicketId);
}

public void UpdateTicket(TicketInfo ticket, string comment)
{
ValidateTicket(ticket);
object[] values = _ticket.Update(ticket.TicketId, comment, ticket.Attributes);
ticket.Update(values);
}

public void CreateTicket(TicketInfo ticket)
{
if (string.IsNullOrEmpty(ticket.Summary)) throw new ArgumentNullException("ticket.Summary");
if (string.IsNullOrEmpty(ticket.Description)) throw new ArgumentNullException("ticket.Description");
if (string.IsNullOrEmpty(ticket.Type)) throw new ArgumentNullException("ticket.Type");
if (string.IsNullOrEmpty(ticket.Priority)) throw new ArgumentNullException("ticket.Priority");
if (string.IsNullOrEmpty(ticket.Component)) throw new ArgumentNullException("ticket.Component");

XmlRpcStruct tempAttributes = new XmlRpcStruct();
foreach (object key in ticket.Attributes.Keys)
{
if ((((string)key) != TicketAttributes.Description) && (((string)key) != TicketAttributes.Summary))
{
tempAttributes.Add(key, ticket.Attributes[key]);
}
}

int id = _ticket.Create(ticket.Summary, ticket.Description, ticket.Attributes);
ticket.TicketId = id;
}

private void ValidateTicket(TicketInfo ticket)
{
if (ticket == null) throw new ArgumentNullException("ticket");
if (ticket.TicketId <= 0)="" throw="" new="" argumentexception("ticketid="" must="" be="" greater="" then="">
}
}



About the only trick here is that some of the date values get returned as unix time stamps so you need to convert them...


public static DateTime ParseUnixTimestamp(double timestamp)
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(timestamp);
}


Because the Syzmk Rich Media Processor product I work on is a clearing house for simple messages among other things... It becomes a small jump from here (about 30 lines of C# code, 5 for a quick and dirty "IronPython" script) to start submitting tasks and bugs via email or mobile... who knows, it could be handy if you get a call while your out of the office and need to log a bug or task.
Read More

pagination and base4

Monorail Pagination with Base4.Net

So, part of today was spent building "digg" style pagination for data I'm pulling out of base4... if you don't use digg, then what I mean is stuff like this:




border="0" />



Flickr and plenty of other sites use the same layout... and it does seem quite a convenient way to work your way through large result sets...



Fairly standard stuff, because I'm using monorail my first pit stop was the pagination helper - however there's a bit of an impedance mismatch here, as the pagination helper is geared towards paging through an IList containing all the records, where as generally speaking you're working with IItemList and wanting to make use of the inherent paging support in base4.net... hmmm... at any rate, this isn't exactly "brilliant" code - but it might prove useful if you messing around with Monorail at home - something I think you should do.

Paging and base4.net

So, first off, I built a small class to represent a "page" from a larger set of query results in my application... it looks like this...


///
/// Represents a page from a query
///

///
public class PagedItemList : IEnumerable
where T: class, IItem
{
private IItemList _items;
private int _pageNumber;
private int _pageSize;
private int _totalCount;

public PagedItemList(IItemList items, int pageSize, int pageNumber, int totalCount)
{
if (items == null) throw new ArgumentNullException("items");
if (pageNumber <= 0)="" throw="" new="" argumentoutofrangeexception("pagenumber",="" "pagenumber="" must="" be="" greater="" then="">
if (pageSize <= 0)="" throw="" new="" argumentoutofrangeexception("pagesize",="" "pagesize="" must="" be="" greater="" then="">
if (totalCount < 0)="" throw="" new="" argumentoutofrangeexception("totalcount",="" "totalcount="" must="" be="" greater="" then="" or="" equal="" to="">

_items = items;
_pageNumber = pageNumber;
_pageSize = pageSize;

_totalCount = totalCount;

}

///

/// The count of items on this page
///

public int PageCount
{
get { return (int)Math.Ceiling(((double)TotalCount) / ((double)PageSize)); }
}

///

/// Index of the first item on this page
///

public int FirstItemIndex
{
get
{
return PageSize * (PageNumber-1);
}
}

///

/// Index of the last item on this page
///

public int LastItemIndex
{
get
{
return Math.Min(FirstItemIndex + (PageSize - 1), TotalCount - 1);
}
}

///

/// 1-relative page number index
///

public int PageNumber
{
get
{
return _pageNumber;
}
}

///

/// The size of each page
///

public int PageSize
{
get { return _pageSize; }
}

///

/// Total number of results returned from the query
///

public int TotalCount
{
get { return _totalCount; }
}

///

/// Number of items on this page
///

public int Count
{
get { return _items.Count; }
}

///

/// the underlying list of items
///

public IItemList Items
{
get { return _items; }
}

public IEnumerator GetEnumerator()
{
return _items.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable)_items).GetEnumerator();
}
}



Pretty mundane, next we just extend my existing repository with a new method for returning a PagedItemList... here's how it looks:


public virtual PagedItemList Find(ObjectQuery query, int pageSize, int pageNumber)
{
if (query == null) throw new ArgumentNullException("query");
if (pageSize <= 0)="" throw="" new="" argumentexception("pagesize="" must="" be="" greater="" then="">
if (pageNumber <= 0)="" throw="" new="" argumentexception("pagenumber="" must="" be="" greater="" then="">

int count = Convert.ToInt32(_context.ExecuteScalar(query.Compile().SELECT_COUNT()));

query.Path.PageSize = pageSize;
query.Path.PageNumber = pageNumber;

return new PagedItemList(Find(query), pageSize, pageNumber, count);
}



For our query we first estabilsh the total count of results, then we just run the query again and return the results for the selected page.

From Base4.Net to Monorail

Now, our PagedItemList is useful by itself, but there's a more feature-complete interface in monorail for representing a "page" of informaton for display logic, used by the pagination helper, it's called IPaginatedPage.


public interface IPaginatedPage : IEnumerable
{
int CurrentIndex { get; }
int LastIndex { get; }
int NextIndex { get; }
int PreviousIndex { get; }
int FirstIndex { get; }
int FirstItem { get; }
int LastItem { get; }
int TotalItems { get; }
bool HasPrevious { get; }
bool HasNext { get; }
bool HasFirst { get; }
bool HasLast { get; }
}


...so, we just build a little adaptor object, using the abstract base page that implementes IPaginatedPage, called "AbstractPage" funnily enough - here I've called my adaptor Base4Page.


public class Base4Page : AbstractPage
where T: class, IItem
{
private PagedItemList _items;

public Base4Page(PagedItemList items)
{
if (items == null) throw new ArgumentNullException("items");
_items = items;

CalculatePaginationInfo(items.FirstItemIndex, items.LastItemIndex,
items.TotalCount, items.PageSize, items.PageNumber);
}

public override System.Collections.IEnumerator GetEnumerator()
{
return _items.GetEnumerator();
}
}



Soo... last of all, where's the pay off?



Well, first thing's first, now you can do this in your controller:


public void Users(int pageSize, int page)
{
ObjectQuery query = new ObjectQuery(typeof(Car));
// not enough set some further critiera...

PropertyBag.Add("Cars", new Base4Page(_carRepository.Find(query, pageSize, page)));
}



At which point, you could do some simple pagination in your views (and pagination examples for existing Monorail websites can be cut 'n' pasted in to give you a head start)...




#foreach($Car in $Cars)

#end
Make Model Year
$!Car.Make $!Car.Model $!Car.Year



And of course it's not much harder to write the logic for displaying "digg" style pagination.... I decided to write it as a helper in C#, because after messing with it for 15 minutes in brail it pissed me off too much (some things in brail are quite annoying compared to normal boo, for instance I couldn't seem to use the "range(...)" builtin, and if your view doesn't have any content after the last <% ... %> block it throws up a compile time exception... Admittedly I haven't done a get latest from the Castle site in a couple of weeks... so this might not actually be a problem any more, or maybe I just don't have the brail engine configure "just right".


public abstract class AbstractDiggPaginationHelper : AbstractHelper
{
public string CreateDiggPagination(IPaginatedPage page, int adjacents)
{
return CreateDiggPagination(page, adjacents, null);
}

public string CreateDiggPagination(IPaginatedPage page, int adjacents, IDictionary queryStringParams)
{
StringBuilder output = new StringBuilder();
WriteLink(output, page.PreviousIndex, "? prev", !page.HasPrevious, queryStringParams);

if (page.LastIndex < (4="" +="" (adjacents="" *="" 2)))="" not="" enough="" links="" to="" make="" it="" worth="" breaking="">
{
WriteNumberedLinks(output, page, 1, page.LastIndex, queryStringParams);
}
else
{
if ((page.LastIndex - (adjacents * 2) > page.CurrentIndex) && // in the middle

(page.CurrentIndex > (adjacents * 2)))
{

WriteNumberedLinks(output, page,
1, 2, queryStringParams);
WriteElipsis(output);

WriteNumberedLinks(output, page,
page.CurrentIndex - adjacents, page.CurrentIndex + adjacents,
queryStringParams);
WriteElipsis(output);

WriteNumberedLinks(output, page,
page.LastIndex - 1, page.LastIndex, queryStringParams);
}
else if (page.CurrentIndex < (page.lastindex="">
{

WriteNumberedLinks(output, page,
1, 2 + (adjacents * 2), queryStringParams);
WriteElipsis(output);

WriteNumberedLinks(output, page,
page.LastIndex - 1, page.LastIndex, queryStringParams);

}

else // at the end
{

WriteNumberedLinks(output, page,
1, 2, queryStringParams);

WriteElipsis(output);

WriteNumberedLinks(output, page,
page.LastIndex - (2 + (adjacents * 2)), page.LastIndex,
queryStringParams);
}
}

WriteLink(output, page.NextIndex, "next ?", !page.HasNext, queryStringParams);
return output.ToString();
}

private void WriteElipsis(StringBuilder builder)
{
builder.Append("...");
}

private void WriteNumberedLinks(StringBuilder builder, IPaginatedPage page, int startIndex, int endIndex, IDictionary queryStringParams)
{
for (int i=startIndex; i<= endindex;="">
{
WriteNumberedLink(builder, page, i, queryStringParams);
}
}

private void WriteLink(StringBuilder builder, int index, string text, bool disabled, IDictionary queryStringParams)
{
if (disabled)
{
builder.AppendFormat("{0}", text);
}
else
{
WritePageLink(builder, index, text, null, queryStringParams);
}
}

private void WriteNumberedLink(StringBuilder builder, IPaginatedPage page, int index, IDictionary queryStringParams)
{
if (index == page.CurrentIndex)
{
builder.AppendFormat("{0}", index);
}
else
{
WritePageLink(builder, index, index.ToString(), null, queryStringParams);
}
}

protected abstract void WritePageLink(StringBuilder builder, int page, String text, IDictionary htmlAttributes, IDictionary queryStringParams);

}



The guts of the helper is in an abstract class, simply so I could test the numbering logic without having to worry about creating a controller & http context (just implement the WritePageLink method with some simple text output ... the implementation is here:


public class DiggPaginationHelper : AbstractDiggPaginationHelper
{

protected override void WritePageLink(StringBuilder builder, int page, String text, IDictionary htmlAttributes, IDictionary queryStringParams)
{
string filePath = "";

if (CurrentContext != null)
{
filePath = CurrentContext.Request.FilePath;
}

if (queryStringParams == null)
{
queryStringParams = new Hashtable();
}

queryStringParams["page"] = page.ToString();

builder.AppendFormat("{3}", filePath, BuildQueryString(queryStringParams), GetAttributes(htmlAttributes), text);
}
}



At which point you can just register it on the controller, and call it with something like ${DiggPaginationHelper.CreateDiggPagination(tracks, 3)} and get yourself a nice little paging display.  The second parameter is the "adjacent" - which can be used to control how many pages are displayed either side of the selected page when dealing with lots of results.



Oh, and of course you might want some CSS to go with it...


/* pagination */

div.pagination {
padding: 3px;
margin: 3px;
}

div.pagination a {
color: #000099;
text-decoration: none;
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #AAAFEE;
}

div.pagination a:hover, div.pagination a:active {
color: #000;
border: 1px solid #000099;
}

div.pagination span.current {
font-weight: bold;
background-color: #000099;
color: #FFF;
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #000099;
}

div.pagination span.disabled {
color: #DDD;
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #EEE;
}




Read More

Slow new's day - first crack at Base4 'n Castle

Base4 & Castle

Well I've been working with HTML & style sheets today (mostly) - It's be a long while since I've done web development, so it was a little tedious, and it did briefly cross my mind that life might be easier if we lived in some kind of dictatorship where style sheets and layout were provided for me by the "man"... at any rate I also managed to fit in a bit of monorail and base4 to keep my brain from freezing over.



I think for this post I might just talk about the way I'm using base4... basically I prototyped some stuff and came to the conclusion that I didn't like the smell of the static StorageContext class in base4 and it's default connection - too hard to test against - not to say that it's a bad idea, just that I couldn't see any easy way to test and mock code  for code that used/consumed it... so I decided to work up a simple alternative using castle's IoC and some generic interfaces...



At this point... If you've been using Castle for more then a couple of week this is all old news I'm sure, so probably better off finding something else to read ;o)

The facility

First off we have a facility:

public class Base4StorageFacility : AbstractFacility
{
protected override void Init()
{
string url = FacilityConfig.Attributes["url"];
if (string.IsNullOrEmpty(url))
{
throw new StorageException("The Base4StorageFacility requires a "url" attribute to be set");
}

Kernel.AddComponentInstance("base4.defaultContext", typeof (IItemContext), StorageContext.NewContext(url));
Kernel.AddComponent("base4.repository", typeof (IRepository<>), typeof (Base4Repository<>));
}
}



A facility extends the container with additional functionality, in this case the only reason we're using a facility (instead of registering the components themselves individually) is because we're using a static method to create our context for a specific base4 connection url.



Moving on from here we can register the facility in the containers configuration, incidentally now I have somewhere pleasant to configure what base4 server I connect to by default in my application.



type="MyProject.Core.Base4StorageFacility, MyProject.Core"
url="tcp://Server:@localhost:999/Base4_default" />



The Repository

Now what about the IRepository<> ? well here's the interface:

public interface IRepository
where T : class, IItem, new()
{
IItemListProxy List();
IItemList FindAll();
IItemList Find(ObjectQuery query);
IItemList Find(string path);
IItemList Find(string path, params string[] replaces);
IItemList Find(ObjectPath path);
IItemList Find(ObjectPath path, ObjectScope scope);
IItemList FindUsingSQL(string SQL);
IItemList FindUsingSQL(string SQL, ObjectScope scope);
T Get(T previous);
T Get(object id);
T Get(ItemKey key);
T Get(string relativeUri);
T FindOne(ObjectQuery query);
T FindOne(string opath);
T FindOne(string path, params string[] replaces);
T FindOne(ObjectPath path);
T FindOneUsingSQL(string SQL);
T FindOneUsingSQL(string SQL, ObjectScope scope);
T FindOne(string opath, ObjectScope scope);
T FindOne(ObjectPath path, ObjectScope scope);
void DeleteAll();
void Delete(ObjectPath path);
void Delete(string path);
void Delete(string path, params string[] replaces);
void Delete(T item);
void Save(T item);
T Create(string Xml);
T Create(XmlReader reader);
T Create();
}


It's pretty much works like IItemContext, except that you avoid having to pass generic parameters to the individual methods because the interface itself has a generic parameter... there's a couple of extras there that I'll cover at the end of this post too.



Conversely, the class Base4Repository implements this interface... which looks like this (or at least the first few methods, I've left the rest out for brevity) - this is similar to what Ivan has done.


public class Base4Repository : IRepository
where T : class, IItem, new()
{
private IItemContext _context;

public Base4Repository(IItemContext context)
{
if (context == null) throw new ArgumentNullException("context");
_context = context;
}

public virtual IItemListProxy List()
{
return _context.List();
}

public virtual IItemList FindAll()
{
return _context.FindAll();
}



Notice that it doesn't have a parameterless constructor, we rely on the container to inject the default IItemContext when creating instances of the Base4Repository...

The Container

Now, by default the container assumes a component has a "singleton" lifestyle, thankfully for a type with a
generic parameter it is per that parameter, so this test case below passes - incidentally if you tend to use lifecycles other then the default, I would strongly suggest adding tests to make sure the lifecycle is actually applied... you can just imagine what happens in a multi-threaded app when a "Message" class has a singleton lifecycle when you expected a transient ;o) you end up with some bizarre behavior that might not be picked up in normal unit tests.

[Test]
public void IsSingleton()
{
IRepository fileRepository1 = container.Resolve<>>();
IRepository fileRepository2 = container.Resolve<>>();
Assert.AreSame(fileRepository1, fileRepository2);

IRepository typeRepository1 = container.Resolve<>>();
IRepository typeRepository2 = container.Resolve<>>();
Assert.AreSame(typeRepository1, typeRepository2);
}



cool, clear as mud?



Right... so moving on from there, in my own components when I need to access data I now use the repository... so I could, for instance, create a monorail controller like this:


public class FileController : BaseController
{
private IRepository _repository;

public FileController(IRepository repository)
{
if (repository == null) throw new ArgumentNullException("repository");
_repository = repository;
}

public void Fetch(Guid fileId)
{
CancelLayout();
CancelView(); // very important
Response.Clear(); // ensure the response is empty

FileBase file = _repository.Get(fileId);
if (file != null)
{
Response.ContentType = file.MimeType;
file.FileContent.CopyTo(Response.OutputStream);
}
else
{
Response.StatusCode = 404;
}
}
}



And everything will just work - now in some cases this isn't convenient (the dependency injection model) so I broke down and
created a static repository class as well, which accesses the default container for the application... basically it has the same interface as IRepository, but they're static methods.. so you can code like this:


Track track = Repository.Get(id);



Personally I'm not actually that keen on this approach - The Syzmk RMP product I've worked on uses the container everywhere, but avoids ever having to access the default container statically...  and if you end up with a class being injected with a large number of dependencies it's often (but not always) a good indication that there's some violation of orthogonality - if only because a class consuming that many dependencies is probably doing more then one thing... a little difficult to pick up on otherwise.



But at any rate It seems pretty good for a website, where I can't see me using more the one container (or even child containers) within the same app domain.

Wrinkles...

Moving beyond that, the last thing I have to say is that originally I was creating new instances and then saving them with a repository like this:

Group group = new Group();
...Repository.Save(group);
...
User user = new User();
...user.Groups.Add(group);
...Repository.Save(user);


However, it all turns to 4 shades of brown when we go to add to a many-to-many collection, like this:


Goat goat = new Goat();
goat.Name = "junk";
Repository.Save(goat);

BoatOfGoats boat = new BoatOfGoats();
boat.Name = "track";
boat.Goats.Add(goat); // <-- "storageexception="" :="" default="" is="" not="" available,="" as="" no="" default="" has="" been="">
Repository.Save(track);



As far as I can tell the event handling for the Add(item) method expects a default context to be assigned... now I'd be tempted to call this a bug, but that's a bit rash till I understand the in's and out's of base4, however there's an easy way round it... and that's to assign the context yourself :) so a fix would be to add this line before the one that bombs:


track.Context = container.Resolve();


But that's pretty kludgey, so instead of using:


Goat goat = new Goat();


I added some Create(...) overloads to the repository to take care of it... and do this:


Genre genre = Repository.Create();

Last of all, though I haven't drawn on the entire implementation, I'm hoping to follow (At least in spirit) some of the work Ayende has done with his NHibernate repository concept as the project progresses, I think it will add a more natural "feel" combined with the repositories for implementing transactions vs. interacting with base4's ObjectTransaction directly, not to mention providing something I can test and mock out easily...  We shall see what actually happens as the project progresses.

Read More

Hail...

Just hailed like hell here, thought a window was going to break ;o)

Read More