something a little bit different...
Nothing to do with code, but how many people are or have watched time trumpet in NZ?
The professional blog of Alex Henderson, Software Engineer
Nothing to do with code, but how many people are or have watched time trumpet in NZ?
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();
}
type="Castle.Facilities.Startable.StartableFacility, Castle.MicroKernel" />
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
}
<>
id="base4host.default"
type="MyProject.Base4Host, MyProject">GoatsAndBoats 11888
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"
/>
[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();
}
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";
}
///
/// 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
}
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="">=>
}
}
public static DateTime ParseUnixTimestamp(double timestamp)
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(timestamp);
}
///
/// 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 IItemListItems
{
get { return _items; }
}public IEnumerator
GetEnumerator()
{
return _items.GetEnumerator();
}System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable)_items).GetEnumerator();
}
}
public virtual PagedItemListFind(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);
}
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; }
}
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();
}
}
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)));
}
Make | Model | Year |
---|---|---|
$!Car.Make | $!Car.Model | $!Car.Year |
Showing $Cars.FirstItem - $Cars.LastItem of $Cars.TotalItems |
#if($Cars.HasFirst) $PaginationHelper.CreatePageLink( 1, "first", null, null) ) #end #if(!$Cars.HasFirst) first #end #if($Cars.HasPrevious) | $PaginationHelper.CreatePageLink( $Cars.PreviousIndex, "prev", null, null) ) #end #if(!$Cars.HasPrevious) | prev #end #if($Cars.HasNext) | $PaginationHelper.CreatePageLink( $Cars.NextIndex, "next",null, null) ) #end #if(!$Cars.HasNext) | next #end #if($Cars.HasLast) | $PaginationHelper.CreatePageLink( $Cars.LastIndex, "last",null, null) ) #end #if(!$Cars.HasLast) | last #end |
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);
}
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);
}
}
/* 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;
}
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)
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<>));
}
}
type="MyProject.Core.Base4StorageFacility, MyProject.Core"
url="tcp://Server:@localhost:999/Base4_default" />
Now what about the IRepository<> ? well here's the interface:
public interface IRepository
where T : class, IItem, new()
{
IItemListProxyList();
IItemListFindAll();
IItemListFind(ObjectQuery query);
IItemListFind(string path);
IItemListFind(string path, params string[] replaces);
IItemListFind(ObjectPath path);
IItemListFind(ObjectPath path, ObjectScope scope);
IItemListFindUsingSQL(string SQL);
IItemListFindUsingSQL(string SQL, ObjectScope scope);
T Get(T previous);
T Get(object id);
T Get(ItemKeykey);
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();
}
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...
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()
{
IRepositoryfileRepository1 = container.Resolve<> >();
IRepositoryfileRepository2 = container.Resolve<> >();
Assert.AreSame(fileRepository1, fileRepository2);IRepository
typeRepository1 = container.Resolve<> >();
IRepositorytypeRepository2 = container.Resolve<> >();
Assert.AreSame(typeRepository1, typeRepository2);
}
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 emptyFileBase file = _repository.Get(fileId);
if (file != null)
{
Response.ContentType = file.MimeType;
file.FileContent.CopyTo(Response.OutputStream);
}
else
{
Response.StatusCode = 404;
}
}
}
Track track = Repository
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.
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);
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);
track.Context = container.Resolve();
Goat goat = new Goat();
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.