A day in the life...

public class GeekEarth : Earth { }
posts - 26, comments - 7859, trackbacks - 0

Thursday, November 10, 2011

ASP MVC Silverlight control not appearing–404 not found

Just a quick tip in case you have deployed your ASP MVC app with silverlight controls in it which are not appearing.  I experienced this recently when deploying to IIS6.  The solution is

  1. Open the IIS manager app (Start->Admin->Internet Information Services (IIS) Manager)
  2. Expand the local computer node
  3. Then expand the Websites nodes
  4. Right-click your website and select “Properties”
  5. Click the “Http Headers” tab
  6. At the bottom of the page click the “MIME Types” button
  7. Click the “New” button
  8. For the extension type in .xap
  9. For the MIME Type type in application/x-silverlight-app

All done Smile

posted @ Thursday, November 10, 2011 11:17 AM | Feedback (95) |

Tuesday, April 12, 2011

Identified associations (maps)

I thought I’d share this approach to having named associations.  Let’s say you have an Address class with a Name property on it, your Employee can have a property like this

1 var homeAddress = new Address(.....); 2 homeAddress.Line1 = "192 Blah Street"; 3 homeAddress.Line2 = "etc"; 4 employee.Addresses["Home"] = homeAddress; 5 6 var workAddress = new Address(.....); 7 workAddress.Line1 = "123 Meh Road"; 8 workAddress.Line2 = "etc"; 9 employee.Addresses["Work"] = workAddress;

 

Once you have created your Employee and Address classes add a private association from Employee to Address named something like _Addresses.  Then add the following code to the Employee class.

1 IdentifiedAssociation<string, NamedPostalAddress> postalAddresses; 2 public IdentifiedAssociation<string, NamedPostalAddress> PostalAddresses 3 { 4 get 5 { 6 if (postalAddresses == null) 7 postalAddresses = new IdentifiedAssociation<string, NamedPostalAddress>( 8 //Delete existing value on unlink 9 true, 10 //Get association 11 () => this._Addresses, 12 //Object matches identifier 13 (address, identifier) => string.Compare(identifier, address.Name, true) == 0, 14 //Set object identifier 15 (address, identifier) => address.Name = identifier); 16 return postalAddresses; 17 } 18 } 19

 

You might want to make the setter for the indexed property (Address.Name in this case) to internal so that only business classes can alter it.  The generic code to implement this identified association is as follows

1 public class IdentifiedAssociation<TIdentifier, TAssociated> : IEnumerable<TAssociated> 2 where TAssociated : IEcoObject 3 { 4 readonly bool DeleteOnUnlink; 5 readonly Func<IList<TAssociated>> GetAssociation; 6 readonly Func<TAssociated, TIdentifier, bool> ObjectMatchesIdentifier; 7 readonly Action<TAssociated, TIdentifier> SetIdentifier; 8 9 public IdentifiedAssociation( 10 bool deleteOnUnlink, 11 Func<IList<TAssociated>> getAssociation, 12 Func<TAssociated, TIdentifier, bool> objectMatchesIdentifier, 13 Action<TAssociated, TIdentifier> setIdentifier) 14 { 15 Contract.Requires(getAssociation != null); 16 Contract.Requires(objectMatchesIdentifier != null); 17 Contract.Requires(setIdentifier != null); 18 19 DeleteOnUnlink = deleteOnUnlink; 20 21 GetAssociation = getAssociation; 22 ObjectMatchesIdentifier = objectMatchesIdentifier; 23 SetIdentifier = setIdentifier; 24 } 25 26 public TAssociated this[TIdentifier identifier] 27 { 28 get 29 { 30 return GetAssociation().SingleOrDefault(x => ObjectMatchesIdentifier(x, identifier)); 31 } 32 set 33 { 34 var currentValue = this[identifier]; 35 if (currentValue != null && currentValue.Equals(value)) 36 return; 37 if (currentValue != null && DeleteOnUnlink) 38 currentValue.AsIObject().Delete(); 39 if (value != null) 40 { 41 GetAssociation().Add(value); 42 SetIdentifier(value, identifier); 43 } 44 } 45 } 46 47 public IEnumerator<TAssociated> GetEnumerator() 48 { 49 return GetAssociation().GetEnumerator(); 50 } 51 52 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 53 { 54 return GetEnumerator(); 55 } 56 } 57

posted @ Tuesday, April 12, 2011 10:57 AM | Feedback (330) |

Saturday, April 02, 2011

A WebServer for MonoTouch that also parses posted form data (HttpListenerRequest)

I found a few examples of web servers for MonoTouch but none of them parsed the data sent in a POST request.  I looked around the web and was unable to find any examples of how to achieve this.  So now that I’ve written it myself I’ve decided to share my own implementation.  This includes not only the code for processing the form post data but also for registering request handlers etc.

Here is an example of how you would use the web server

1 public BookUploadViewController() 2 : base("BookUploadViewController", null) 3 { 4 RequestHandler = new DefaultRequestHandler(); 5 var defaultActionHandlerFactory = new DefaultActionHandlerFactory(); 6 RegisterActionHandlers(defaultActionHandlerFactory); 7 RequestHandler.AddActionHandlerFactory(defaultActionHandlerFactory); 8 9 WebServer = new EmbeddedWebServer(RequestHandler); 10 } 11 12 void RegisterActionHandlers(DefaultActionHandlerFactory factory) 13 { 14 factory.RegisterHandler( 15 request => request.RawUrl == "/", 16 request => new IndexActionHandler(request) 17 ); 18 factory.RegisterHandler( 19 request => 20 string.Compare(request.RawUrl, "/Upload", true) == 0 && 21 string.Compare(request.HttpMethod, "POST", true) == 0, 22 request => new UploadActionHandler(request) 23 ); 24 } 25 26 public override void ViewDidAppear(bool animated) 27 { 28 base.ViewDidAppear(animated); 29 StatusLabel.Text = string.Format("Server listening on\r\nhttp://{0}:8080", GetIPAddress ()); 30 WebServer.Start(8080); 31 } 32 33 public override void ViewDidDisappear (bool animated) 34 { 35 base.ViewDidDisappear(animated); 36 WebServer.Stop(); 37 } 38

 

And here are two app specific examples of request handlers

1 class IndexActionHandler : DefaultActionHandler 2 { 3 public IndexActionHandler(HttpListenerRequest request) 4 : base(request) 5 { 6 } 7 8 public override ActionResult Execute() 9 { 10 var result = new HtmlResult(); 11 result.AppendLine("<html>"); 12 result.AppendLine("<body>"); 13 result.AppendLine("<h1>Upload an image</h1>"); 14 result.AppendLine("<form action='/Upload' enctype='multipart/form-data' method='post'>"); 15 result.AppendLine ("<input name='Image' type='file'/><br/>"); 16 result.AppendLine("<input name='Upload' type='submit' text='Upload'/>"); 17 result.AppendLine("</form>"); 18 result.AppendLine("</body>"); 19 result.AppendLine("</html>"); 20 return result; 21 } 22 } 23 24 class UploadActionHandler : DefaultActionHandler 25 { 26 public UploadActionHandler(HttpListenerRequest request) 27 : base(request) 28 { 29 } 30 31 public override ActionResult Execute() 32 { 33 string errorMessage = null; 34 var file = FormData.GetFile("Image"); 35 if (file == null 36 || file.FileData == null 37 || file.FileData.Length == 0 38 || string.IsNullOrEmpty(file.FileName)) 39 errorMessage = "No image uploaded"; 40 41 if (errorMessage == null) 42 ProcessFile(file); 43 44 var result = new HtmlResult(); 45 result.AppendLine("<html>"); 46 result.AppendLine("<body>"); 47 if (errorMessage == null) 48 result.AppendLine("<h1>File uploaded successfully</h1>"); 49 else 50 { 51 result.AppendLine("<h1>Error</h1>"); 52 result.AppendLine("<h2>" + errorMessage + "</h2>"); 53 } 54 result.AppendLine("</body>"); 55 result.AppendLine("</html>"); 56 return result; 57 } 58 59 void ProcessFile(MultiPartStreamFileValue postedFile) 60 { 61 string fileName = "Where to save the file"; 62 using (var fileStream = 63 new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) 64 { 65 fileStream.Write(postedFile.FileData, 0, postedFile.FileData.Length); 66 } 67 } 68 69 } 70

 

 

 

You can download the files here.

posted @ Saturday, April 02, 2011 6:58 PM | Feedback (327) | Filed Under [ Code ]

Monday, March 14, 2011

Aurora on the iPad

I fancied indulging my side interest of making music by buying some music making apps on the iPad. One of which is called "Aurora" from 4pockets.com

Because Apple don't allow trial versions of apps in the Apple store people often release a "lite" version of their app free of charge in order to let potential customers try a limited version of their app before purchasing. I was disappointed to see that this app didn't have such a version, but decided that at a price of £24 it must be pretty good so I took the plunge and bought it.

What a mistake! It is absolutely awful! Even the demo tunes (which should show it off at its best) were terrible! I looked at the Apple store to see if there is any way to get a refund, but the T&C say "all purchases are final", great!

I thought that perhaps the company that developed the app might be good with customer relations and, like myself, only want money from people who actually like my work and think it is worth paying for. Unfortunately it seems as though their philosophy is more like "We want every penny we can get our hands on, even if it means selling over priced junk to unhappy customers."

On the other hand, the very reasonably priced £5 app "MorphWhiz" is great fun!

Mr P Morris to sales
Hi

I bought aurora for my iPad. With a lack of a "lite" version I was unable to evaluate it without purchasing it. Having purchased it I have found I really isn't my cup of tea at all. Is there a way to get a refund?

Pete



sales@4pockets.com to me
Dear Sir,

You will need to talk to Apple

Regards
Bill Muir



Peter Morris to sales
Hi Bill

I've read the terms and conditions and it seems that there is no process for issuing refunds after the application has been downloaded, which is why I wrote to you directly.


Regards

Pete



sales@4pockets.com to me
Dear Sir,

Your transaction is with Apple. We get no data as to who has purchased product via Apple. We only get dates, number of sales, value and country details. Sorry we are unable to help.

Regards
Bill Muir



Peter Morris to sales
Yes, I can see that being potential for fraudulent claims :)

What if I were to send you proof of purchase when I receive my invoice, would that be sufficient?


Pete



sales@4pockets.com to me
Dear Sir,

We can not help you. You are a client of Apple not 4pockets.com

Regards
Bill Muir



Peter Morris to sales
Okay, that's a real cop-out answer. I will take this to mean "Dear sir, we intend to grab every penny we can and to hell with customer satisfaction." I shall ensure my rating on your product accurately reflects your sentiments.

Thanks for taking your share of £23 of my hard earned money for an app not worth more than £5.

Shame on you.


Pete

posted @ Monday, March 14, 2011 6:17 PM | Feedback (324) | Filed Under [ Misc ]

Thursday, February 10, 2011

Additional visualisations in ECO

My model needs to take snapshots of certain data and to do this I have various snapshot classes.

Sometimes you might snapshot an aggregate root such as PurchaseOrder and expect all PurchaseOrderLines to be snapshotted along with it. Sometimes though the Parent/Child relationship is not part of an aggregate root and you want the child snapshot to create the parent snapshot, for example when snapshotting an Address you might want to snapshot the Country.

So when I was looking at my snapshot diagrams it wasn't immediately obviously which behaviour you get. Hans showed me this article: http://theblog.capableobjects.com/2009/12/catching-more-information-in-your-model.html

Now I have added symbols + tagged value definitions to my model and as a consequence the diagrams show me visually which way the snapshots work. Brilliant!

posted @ Thursday, February 10, 2011 11:01 AM | Feedback (263) | Filed Under [ Code ]

Monday, January 10, 2011

How many unique pizza combinations does Domino’s Pizza have?

Okay, a weird topic I know but I saw a question asked in NewScientist querying whether the Domino’s Pizza claim of 1.8 billion combinations is possible.  Here is the answer I am submitting:

If we have 4 options on the menu (ABCD) and choose 1 topping we have 4 unique combinations. If however we choose 2 toppings we have 6 unique combinations.  As toppings in a different order are considered the same combination this means that when choosing the second topping we must choose from a sub-set of the entire menu. For example, when working out how many combinations start with A the second selection is limited to B..D, but when working out how many start with B the second selection is limited to C..D (because BA is the same combination as AB.) Domino's where I live has a menu of 21 toppings. So the valid options for the first selection when choosing 2 toppings are 1..20 (20 because we have 1 more selection to make after this), the valid options for the second selection are (first selection + 1)..21 (21 because there are no more selections to make after this.)

Not only does Domino's have 21 toppings on their menu, but you have the option of single/double which doubles the amount of options per topping, however you are only permitted a maximum of 9 toppings per pizza which brings the number of combinations down significantly.To bring the number of unique combinations back up again we have to take into account that the customer may order between 0..9 toppings, so we have to work out the iterations for each and add them together, where zero toppings is a single choice.  Finally, in addition to the toppings you can choose from 3 sauces (single/double), 2 cheeses (single/double), and from 6 bases - increasing the total combinations by 6x4x6 = 144!  I do not consider pizza size a “combination”.

I'm not much of a mathematician myself so I wrote a simple piece of recursive code to work out the result, which by the way is 200,407,536 - In short, the answer is "No."

And here is the source code to work it out:

1 void ButtonCompute_Click(object sender, EventArgs e) 2 { 3 long combinations = 0; 4 for (int toppingsToAdd = 1; toppingsToAdd <= 9; toppingsToAdd++) 5 combinations += CalculateCombinations(21, toppingsToAdd); 6 7 //Add one combination for "No toppings" 8 combinations++; 9 10 //Take into account the non topping variations 11 combinations = combinations 12 * 6 //Multiply by 3 sauces, single or double 13 * 4 //Multiply by 2 cheeses, single or double 14 * 6; //Multiply by 6 bases 15 16 textBox1.Text = string.Format("Combinations: {0}", combinations); 17 } 18 19 long CalculateCombinations(int toppingsAvailable, int toppingsToAdd) 20 { 21 if (toppingsToAdd == 1) 22 return toppingsAvailable * 2; //Single or double 23 24 long result = 0; 25 int toppingsToAddAfterThis = toppingsToAdd - 1; 26 checked 27 { 28 for ( 29 //Start at highest value, e.g. 21 options 30 int currentChoice = toppingsAvailable; 31 //Keep enough options to fulfill the rest of the choices 32 currentChoice > toppingsToAddAfterThis; 33 currentChoice--) 34 result += CalculateCombinations(currentChoice - 1, toppingsToAddAfterThis); 35 } 36 return result; 37 } 38

posted @ Monday, January 10, 2011 11:10 AM | Feedback (286) | Filed Under [ Code ]

Sunday, December 19, 2010

Creating a pooled lifetime manager for Unity

My application has a child container per request, this is because there are a lot of services used in each request which depend upon an IUnitOfWork.  I thought it would be nice if I could define a pool of these IUnitOfWork instances so that any cost involved in creating them is reduced, they can just be reused in a round-robin fashion.  Well, more accurately, the object space (EcoSpace) on which they depend can anyway.

A pool can now be registered like so…

1 //Must be called once, when the container is created 2 container.AddNewExtension<PooledLifetimeExtension>(); 3 //To register a pooled item 4 container.RegisterType 5 < 6 ISomeItemThatIsExpensiveToCreate, 7 SomeItemThatIsExpensiveToCreate 8 >(new PooledLifetimeManager(10));

The number 10 specifies how many instances at once may be stored in the pool.  If an instance requests an item from the pool when it is empty it will get a new instance, it’s only at the point where an instance is returned to the pool that the MaxPoolSize is honoured – if the pool is not full then the instance goes back into the pool, if the pool is already full then Dispose is called on it instead.  Of course you should be careful when pooling that your pooled items don’t hold onto other injected references which are also pooled as that will cause problems.

I have also added the following interface so that an instance can (optionally) specify whether or not its current state should permit it to go back into the pool, and two methods which indicate to the instance when it has been retrieved from a pool or is about to be returned to the pool – in my case I deactivated the object space upon returning in order to flush any object cache, and reactivated it again when retrieved from the pool.

 

1 public interface IPooledResource 2 { 3 bool CanReturnToPool { get; } 4 void RetrievedFromPool(); 5 void ReturningToPool(); 6 }

 

It’s important to note that this only works with child containers.  You should create your main controller as a kind of template by registering your request specific services (like IUnitOfWork) with HierarchicalLifetimeManager.  The PooledLifetimeManager will work in the same way as HierarchicalLifetimeManager except for the addition of pooling, and it will automatically attempt to return items to the pool once the child container is disposed of.

 

1 //PooledLifetimeExtension.cs 2 using Microsoft.Practices.Unity; 3 using Microsoft.Practices.Unity.ObjectBuilder; 4 5 namespace YourNameHere.Infrastructure.Unity 6 { 7 public class PooledLifetimeExtension : UnityContainerExtension 8 { 9 protected override void Initialize() 10 { 11 // Add to type mapping stage so that it runs before the lifetime stage 12 Context.Strategies.AddNew<PooledLifetimeStrategy> 13 (UnityBuildStage.TypeMapping); 14 } 15 } 16 } 17 18 19 20 //PooledLifetimeManager.cs 21 using System; 22 using System.Collections.Generic; 23 using Microsoft.Practices.Unity; 24 using Microsoft.Practices.ObjectBuilder2; 25 26 namespace YourNameHere.Infrastructure.Unity 27 { 28 public class PooledLifetimeManager : LifetimeManager 29 { 30 readonly PooledLifetimeManagerPool Pool; 31 32 public PooledLifetimeManager(int maxPoolSize) 33 { 34 Pool = new PooledLifetimeManagerPool(maxPoolSize); 35 } 36 37 internal PooledLifetimeManagerPoolConsumer CreatePoolUserForChildContainer() 38 { 39 return new PooledLifetimeManagerPoolConsumer(Pool); 40 } 41 42 #region GetValue/SetValue/RemoveValue all redundant 43 public override object GetValue() 44 { 45 throw new InvalidOperationException(); 46 } 47 48 public override void SetValue(object newValue) 49 { 50 throw new InvalidOperationException(); 51 } 52 53 public override void RemoveValue() 54 { 55 throw new InvalidOperationException(); 56 } 57 #endregion 58 } 59 60 internal class PooledLifetimeManagerPool 61 { 62 readonly int MaxPoolSize; 63 readonly object SyncRoot = new object(); 64 readonly Queue<object> Queue = new Queue<object>(); 65 66 public PooledLifetimeManagerPool(int maxPoolSize) 67 { 68 if (maxPoolSize < 0) 69 throw new ArgumentOutOfRangeException( 70 "MaxPoolSize", "Cannot be less than zero"); 71 MaxPoolSize = maxPoolSize; 72 } 73 74 internal object RetrieveFromPool() 75 { 76 object result = null; 77 lock (SyncRoot) 78 if (Queue.Count > 0) 79 result = Queue.Dequeue(); 80 IPooledResource resultAsIPooledResource = result as IPooledResource; 81 if (resultAsIPooledResource != null) 82 resultAsIPooledResource.RetrievedFromPool(); 83 return result; 84 } 85 86 internal void ReturnToPool(object instance) 87 { 88 if (instance == null) 89 throw new ArgumentNullException("Instance"); 90 IPooledResource instanceAsIPooledResource = instance as IPooledResource; 91 if (instanceAsIPooledResource != null) 92 { 93 if (!instanceAsIPooledResource.CanReturnToPool) 94 return; 95 instanceAsIPooledResource.ReturningToPool(); 96 } 97 bool wentIntoPool = false; 98 lock (SyncRoot) 99 if (Queue.Count < MaxPoolSize) 100 { 101 Queue.Enqueue(instance); 102 wentIntoPool = true; 103 } 104 105 //Dispose any instances which did not go back into the pool 106 if (!wentIntoPool && instance is IDisposable) 107 ((IDisposable)instance).Dispose(); 108 } 109 } 110 111 public class PooledLifetimeManagerPoolConsumer : 112 ContainerControlledLifetimeManager, IDisposable 113 { 114 readonly PooledLifetimeManagerPool SharedPool; 115 object Instance; 116 117 internal PooledLifetimeManagerPoolConsumer( 118 PooledLifetimeManagerPool sharedPool) 119 { 120 if (sharedPool == null) 121 throw new ArgumentNullException("SharedPool"); 122 SharedPool = sharedPool; 123 } 124 125 protected override object SynchronizedGetValue() 126 { 127 if (Instance == null) 128 Instance = SharedPool.RetrieveFromPool(); 129 return Instance; 130 } 131 132 protected override void SynchronizedSetValue(object newValue) 133 { 134 if (Instance != null && newValue == null) 135 SharedPool.ReturnToPool(Instance); 136 Instance = newValue; 137 } 138 139 public override void RemoveValue() 140 { 141 throw new InvalidOperationException(); 142 } 143 144 void IDisposable.Dispose() 145 { 146 SetValue(null); 147 base.Dispose(); 148 } 149 } 150 } 151 152 153 154 //PooledLifetimeStrategy.cs 155 using Microsoft.Practices.ObjectBuilder2; 156 157 namespace YourNameHere.Infrastructure.Unity 158 { 159 public class PooledLifetimeStrategy : BuilderStrategy 160 { 161 public override void PreBuildUp(IBuilderContext context) 162 { 163 PooledLifetimeManager activeLifetime = 164 context.PersistentPolicies.Get<ILifetimePolicy>(context.BuildKey) 165 as PooledLifetimeManager; 166 if (activeLifetime != null) 167 { 168 // did this come from the local container or the parent? 169 var localLifetime = 170 context.PersistentPolicies.Get<ILifetimePolicy>( 171 context.BuildKey, true); 172 if (localLifetime == null) 173 { 174 // came from parent, add a ContainerControlledLifetime here 175 var newLifeTime = activeLifetime.CreatePoolUserForChildContainer(); 176 context.PersistentPolicies.Set<ILifetimePolicy>( 177 newLifeTime, context.BuildKey); 178 //Make sure it gets disposed 179 //so that it can return its reference to the pool 180 context.Lifetime.Add(newLifeTime); 181 } 182 } 183 } 184 } 185 } 186 187 188 189 //PooledResourceIntf.cs 190 namespace YourNameHere.Infrastructure.Unity 191 { 192 public interface IPooledResource 193 { 194 bool CanReturnToPool { get; } 195 void RetrievedFromPool(); 196 void ReturningToPool(); 197 } 198 } 199

posted @ Sunday, December 19, 2010 6:33 PM | Feedback (320) |

Unity child containers + ASP MVC = Memory leak

I was making some speed improvements to my current ASP MVC application.  One of the things I did was to change from creating a completely new IUnityContainer for each request over to creating one master (template) container with all the services registered with HierarchicalLifetimeManager.  Then whenever a controller is required my ControllerFactory does this

1 if (controllerType == null) 2 return null; 3 var requestContainer = Container.CreateChildContainer(); 4 return (IController) requestContainer.Resolve(controllerType);

That’s all nice and simple, however it causes a memory leak.  The reason is that the parent container holds a reference to all of its children.  The only way to resolve this is to Dispose of the child container.  Now obviously we cannot dispose of it in the controller factory because we need to return a fully working Controller.  So the next thing I tried was to dispose of the request container in IControllerFactory.ReleaseController.  The problem with this is that the controller is released before the view is rendered, so if your view depends on objects which retrieve their state from some kind of object space then this is too soon to dispose of the request container.  So ultimately I couldn’t find anywhere in the ASP MVC framework into which I could hook an event which gets fired after the request has finished.

To solve the problem my IControllerFactory no longer returns the controller type requested.  Instead at runtime it creates a descendant class which implements IController directly and then passes the request on to the original controller class.  The type returned stores a reference to the per-request IUnityContainer and then does this

1 void IController.Execute(RequestContext context) 2 { 3 try 4 { 5 base.Execute(context); 6 } 7 finally 8 { 9 RequestUnityContainer.Dispose(); 10 } 11 }

So now we have a bit of code in our controller which disposes of the per-request unity container after the entire request has been processed.  The ControllerFactory code only needs to be changed like so

1 protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) 2 { 3 if (controllerType == null) 4 return null; 5 IUnityContainer requestContainer = Container.CreateChildContainer(); 6 ControllerBase controller = ContainerDisposingControllerFactory.Create(requestContainer, controllerType); 7 return controller; 8 }

 

The code required is listed below…

1 //ContainerDisposingControllerIntf.cs 2 using Microsoft.Practices.Unity; 3 4 namespace YourNameHere.Infrastructure.Web 5 { 6 public interface IContainerDisposingController 7 { 8 IUnityContainer IContainerDisposingController_UnityContainer { get; set; } 9 } 10 } 11 12 13 //ContainerDisposingControllerFactory.cs 14 using System; 15 using System.Collections.Generic; 16 using System.Linq; 17 using System.Reflection; 18 using System.Reflection.Emit; 19 using System.Threading; 20 using System.Web.Mvc; 21 using Microsoft.Practices.Unity; 22 23 namespace YourNameHere.Infrastructure.Web 24 { 25 public static class ContainerDisposingControllerFactory 26 { 27 const string UnityContainerBackingFieldName = "IContainerDisposingController_BackingField"; 28 static MethodInfo DisposeMethodInfo = typeof(IDisposable).GetMethod("Dispose"); 29 static readonly Dictionary<Type, Type> InterceptingType_BySuperClass = new Dictionary<Type, Type>(); 30 static ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); 31 32 public static ControllerBase Create(IUnityContainer containerToDispose, Type controllerType) 33 { 34 try 35 { 36 return CreateTypeAndInstance(containerToDispose, controllerType); 37 } 38 catch 39 { 40 containerToDispose.Dispose(); 41 throw; 42 } 43 } 44 45 private static ControllerBase CreateTypeAndInstance(IUnityContainer containerToDispose, Type controllerType) 46 { 47 Type interceptingControllerType; 48 Locker.EnterUpgradeableReadLock(); 49 try 50 { 51 if (!InterceptingType_BySuperClass.TryGetValue(controllerType, out interceptingControllerType)) 52 { 53 Locker.EnterWriteLock(); 54 try 55 { 56 interceptingControllerType = CreateInterceptingControllerType(controllerType); 57 InterceptingType_BySuperClass[controllerType] = interceptingControllerType; 58 } 59 finally 60 { 61 Locker.ExitWriteLock(); 62 } 63 } 64 } 65 finally 66 { 67 Locker.ExitUpgradeableReadLock(); 68 } 69 70 var result = (Controller)containerToDispose.Resolve(interceptingControllerType); 71 var resultAsIUnityContainerController = (IContainerDisposingController)result; 72 resultAsIUnityContainerController.IContainerDisposingController_UnityContainer = containerToDispose; 73 return result; 74 } 75 76 static Type CreateInterceptingControllerType(Type controllerType) 77 { 78 if (!typeof(ControllerBase).IsAssignableFrom(controllerType)) 79 throw new ArgumentException("ControllerType does not descend from ControllerBase"); 80 81 string guid = Guid.NewGuid().ToString(); 82 var assemblyName = new AssemblyName(guid); 83 var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( 84 assemblyName, 85 AssemblyBuilderAccess.Run); 86 var moduleBuilder = assemblyBuilder.DefineDynamicModule(guid); 87 var typeBuilder = moduleBuilder.DefineType( 88 guid, 89 TypeAttributes.Class | TypeAttributes.Public, 90 controllerType); 91 CreateConstructor(controllerType, typeBuilder); 92 FieldBuilder unityContainerBackingFieldBuilder; 93 ImplementIContainerDisposingController(typeBuilder, out unityContainerBackingFieldBuilder); 94 ImplementIController(controllerType, typeBuilder, unityContainerBackingFieldBuilder); 95 return typeBuilder.CreateType(); 96 } 97 98 static void CreateConstructor(Type controllerType, TypeBuilder typeBuilder) 99 { 100 var constructorInfo = controllerType.GetConstructors() 101 .OrderByDescending(x => x.GetParameters().Count()) 102 .FirstOrDefault(); 103 if (constructorInfo == null) 104 return; 105 ParameterInfo[] constructorParameters = 106 constructorInfo.GetParameters().ToArray(); 107 Type[] parameterTypes = constructorParameters.Select(x => x.ParameterType).ToArray(); 108 109 var constructorBuilder = typeBuilder.DefineConstructor( 110 MethodAttributes.Public, 111 CallingConventions.Standard, 112 parameterTypes); 113 114 //Define the parameters for our new constructor 115 for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++) 116 constructorBuilder.DefineParameter( 117 argumentIndex + 1, 118 constructorParameters[argumentIndex].Attributes, 119 constructorParameters[argumentIndex].Name); 120 121 var bodyGenerator = constructorBuilder.GetILGenerator(); 122 bodyGenerator.Emit(OpCodes.Ldarg_0); 123 for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++) 124 bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + 1); 125 bodyGenerator.Emit(OpCodes.Call, constructorInfo); 126 bodyGenerator.Emit(OpCodes.Ret); 127 } 128 129 static void ImplementIContainerDisposingController(TypeBuilder typeBuilder, out FieldBuilder unityContainerBackingFieldBuilder) 130 { 131 typeBuilder.AddInterfaceImplementation(typeof(IContainerDisposingController)); 132 unityContainerBackingFieldBuilder = typeBuilder.DefineField( 133 UnityContainerBackingFieldName, 134 typeof(IUnityContainer), 135 FieldAttributes.Private); 136 137 var propertyAccessorAttributes = 138 MethodAttributes.Public | MethodAttributes.SpecialName | 139 MethodAttributes.HideBySig | MethodAttributes.Virtual; 140 141 var getterBuilder = typeBuilder.DefineMethod( 142 "get_IContainerDisposingController_UnityContainer", 143 propertyAccessorAttributes, 144 typeof(IUnityContainer), 145 Type.EmptyTypes); 146 var getterGenerator = getterBuilder.GetILGenerator(); 147 getterGenerator.Emit(OpCodes.Ldarg_0); 148 getterGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder); 149 getterGenerator.Emit(OpCodes.Ret); 150 151 var setterBuilder = typeBuilder.DefineMethod( 152 "set_IContainerDisposingController_UnityContainer", 153 propertyAccessorAttributes, 154 null, 155 new Type[] { typeof(IUnityContainer) }); 156 var setterGenerator = setterBuilder.GetILGenerator(); 157 setterGenerator.Emit(OpCodes.Ldarg_0); 158 setterGenerator.Emit(OpCodes.Ldarg_1); 159 setterGenerator.Emit(OpCodes.Stfld, unityContainerBackingFieldBuilder); 160 setterGenerator.Emit(OpCodes.Ret); 161 } 162 163 static void ImplementIController(Type controllerType, TypeBuilder typeBuilder, FieldBuilder unityContainerBackingFieldBuilder) 164 { 165 typeBuilder.AddInterfaceImplementation(typeof(IController)); 166 MethodInfo baseMethod = null; 167 Type implementingType = controllerType; 168 do 169 { 170 baseMethod = implementingType.GetMethod( 171 "Execute", 172 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 173 implementingType = implementingType.BaseType; 174 } 175 while (baseMethod == null && implementingType != null); 176 if (baseMethod == null) 177 throw new NotImplementedException(controllerType.Name + " does not implement IController.Execute"); 178 179 var methodParameters = baseMethod.GetParameters(); 180 var methodBuilder = typeBuilder.DefineMethod( 181 typeof(IController).Name + ".Execute", 182 MethodAttributes.Public | MethodAttributes.Virtual | 183 MethodAttributes.ReuseSlot | MethodAttributes.HideBySig, 184 null, 185 methodParameters.Select(x => x.ParameterType).ToArray()); 186 //Define the parameters for our new constructor 187 for (int argumentIndex = 0; argumentIndex < methodParameters.Length; argumentIndex++) 188 methodBuilder.DefineParameter( 189 argumentIndex + 1, 190 methodParameters[argumentIndex].Attributes, 191 methodParameters[argumentIndex].Name); 192 193 var bodyGenerator = methodBuilder.GetILGenerator(); 194 bodyGenerator.BeginExceptionBlock(); 195 bodyGenerator.Emit(OpCodes.Ldarg_0); 196 for (int argumentIndex = 0; argumentIndex < methodParameters.Length; argumentIndex++) 197 bodyGenerator.Emit(OpCodes.Ldarg_S, argumentIndex + 1); 198 bodyGenerator.Emit(OpCodes.Call, baseMethod); 199 bodyGenerator.BeginFinallyBlock(); 200 bodyGenerator.Emit(OpCodes.Ldarg_0); 201 bodyGenerator.Emit(OpCodes.Ldfld, unityContainerBackingFieldBuilder); 202 bodyGenerator.Emit(OpCodes.Call, DisposeMethodInfo); 203 bodyGenerator.EndExceptionBlock(); 204 bodyGenerator.Emit(OpCodes.Ret); 205 typeBuilder.DefineMethodOverride(methodBuilder, baseMethod); 206 } 207 } 208 }

posted @ Sunday, December 19, 2010 1:33 PM | Feedback (285) | Filed Under [ Code ]

Tuesday, November 30, 2010

ECO atomic operations

I’ve just created a simple class which I thought I’d share.

Account1.Balance += 10; Account2.Balance -= 10;

 

In this case we might expect the second line to throw an exception if the adjustment is not permitted, but the first line has already executed.  Obviously this isn’t a problem because we simply wouldn’t update the database, but sometimes you want an operation to occur in memory as an atomic operation; like this

 

using (var atomicOperation = new AtomicOperation(EcoSpace)) { Account1.Balance += 10; Account2.Balance -= 10; atomicOperation.Commit(); }

And so that is exactly what I wrote.  The class uses the UndoService to create/merge/remove undo blocks so it is even possible to nest atomic operations.

 

public class AtomicOperation : IDisposable { IUndoService UndoService; bool IsResolved; string UndoBlockName; public AtomicOperation(IUnitOfWork unitOfWork) { Contract.Requires(unitOfWork != null); Contract.Requires(unitOfWork.ObjectSpace != null); UndoBlockName = Guid.NewGuid().ToString(); UndoService = unitOfWork.ObjectSpace.GetEcoService<IUndoService>(); UndoService.StartUndoBlock(UndoBlockName); } public void Commit() { if (IsResolved) throw new InvalidOperationException("AtomicOperation has already been committed or rolled back"); IsResolved = true; int index = UndoService.UndoList.IndexOf(UndoBlockName); if (index > 0) { string nameOfBlockAbove = UndoService.UndoList[index - 1].Name; UndoService.UndoList.MergeBlocks(nameOfBlockAbove, UndoBlockName); } else { if (index > 0) UndoService.UndoList.RemoveBlock(UndoBlockName); if (UndoService.RedoList.IndexOf(UndoBlockName) > 0) UndoService.RedoList.RemoveBlock(UndoBlockName); } } public void Rollback() { if (IsResolved) throw new InvalidOperationException("AtomicOperation has already been committed or rolled back"); IsResolved = true; if (UndoService.UndoList.IndexOf(UndoBlockName) > -1) UndoService.UndoBlock(UndoBlockName); if (UndoService.RedoList.IndexOf(UndoBlockName) > -1) UndoService.RedoList.RemoveBlock(UndoBlockName); } void IDisposable.Dispose() { GC.SuppressFinalize(this); if (!IsResolved) Rollback(); } ~AtomicOperation() { throw new InvalidOperationException("AtomicOperation was not disposed, try using()"); } }

posted @ Tuesday, November 30, 2010 8:40 PM | Feedback (262) |

Friday, November 26, 2010

ASP MVC CheckListBox

I needed to present the user with a list of objects from which they could select multiple items.  There is a MultiSelectList class in ASP MVC so I looked into how to use that.  It would seem that to use this class we need to use Html.ListBox.  I think this is a poor choice because it requires the user to hold down the Control key to select additional options, and it is too easy to deselect all of your values accidentally by clicking the control accidentally without the Control key held down.

What I really wanted was something like a CheckListBox, a list of items with a check box next to them, so that’s what I have implemented.  Here is an example of how to set up the view data for my CheckListBox extension.

public ActionResult Index() { var availableItems = new List<MyItem>(); availableItems.Add(new MyItem("A", "One")); availableItems.Add(new MyItem("B", "Two")); availableItems.Add(new MyItem("C", "Three")); availableItems.Add(new MyItem("D", "Four")); var selectedItems = availableItems.Skip(1).Take(2); ViewData["Items"] = CheckListBoxItems.Create( availableItems, x => x.Code, x => x.Name, x => selectedItems.Contains(x)); return View(); } [HttpPost] public ActionResult Index(CheckListBoxItems items) { return Index(); }

 

Make sure that when your application starts you register the custom binder for CheckListBoxItems

protected void Application_Start() { ... ModelBinders.Binders.Add( typeof(CheckListBoxItems), new CheckListBoxItemsModelBinder()); }

 

Here is the first example of how you can mark up your view html.

<%: Html.CheckListBox("Items", (CheckListBoxItems)ViewData["Items"]) %> <!-- Outputs the following HTML <input type="hidden" name="Items[A].Key" value="A"/> <input type="checkbox" name="Items[A].Selected" value="true" />&nbsp;One<br/> <input type="hidden" name="Items[B].Key" value="B"/> <input type="checkbox" name="Items[B].Selected" value="true" checked />&nbsp;Two<br/> <input type="hidden" name="Items[C].Key" value="C"/> <input type="checkbox" name="Items[C].Selected" value="true" checked />&nbsp;Three<br/> <input type="hidden" name="Items[D].Key" value="D"/> <input type="checkbox" name="Items[D].Selected" value="true" />&nbsp;Four<br/> -->

 

And here is another example where you can pass in the HTML to use for each item in the list.  The HTML is just a string used in String.Format where {0} is the check box html and {1} is where the text will be displayed.  In the following example I pass the parameters in in the order 1,0 because I want the text first followed by the check box control.

<table> <tr> <th>Item</th> <th>Selected</th> </tr> <%: Html.CheckListBox( "Items", (CheckListBoxItems)ViewData["Items"], "<tr><td>{1}</td><td>{0}</td></tr>") %> </table> <!-- Outputs the following HTML <table> <tr> <th>Item</th> <th>Selected</th> </tr> <tr> <td>One</td> <td> <input type="hidden" name="Items[A].Key" value="A"/> <input type="checkbox" name="Items[A].Selected" value="true" /> </td> </tr> <tr> <td>Two</td> <td> <input type="hidden" name="Items[B].Key" value="B"/> <input type="checkbox" name="Items[B].Selected" value="true" checked /> </td> </tr> <tr> <td>Three</td> <td> <input type="hidden" name="Items[C].Key" value="C"/> <input type="checkbox" name="Items[C].Selected" value="true" checked /> </td> </tr> <tr> <td>Four</td> <td> <input type="hidden" name="Items[D].Key" value="D"/> <input type="checkbox" name="Items[D].Selected" value="true" /> </td> </tr> </table> -->

 

Here is the source code:

//CheckListBoxItem.cs using System.Collections.Generic; namespace System.Web.Mvc { public class CheckListBoxItem { public string Key { get; set; } public string Text { get; set; } public bool Selected { get; set; } } public class CheckListBoxItems : List<CheckListBoxItem> { public static CheckListBoxItems Create<T>( IEnumerable<T> source, Func<T, string> key, Func<T, string> text, Func<T, bool> selected) { var result = new CheckListBoxItems(); foreach (T item in source) { CheckListBoxItem newItem = new CheckListBoxItem(); result.Add(newItem); newItem.Key = key(item); newItem.Text = text(item); newItem.Selected = selected(item); } return result; } } } //CheckListBoxItems.cs using System.Collections.Generic; using System.Linq; using System.Text; namespace System.Web.Mvc.Html { public static class CheckListBoxExtensions { public static MvcHtmlString CheckListBox(this HtmlHelper htmlHelper, string name, IEnumerable<CheckListBoxItem> items) { return htmlHelper.CheckListBox(name, items, "{0}&nbsp;{1}<br/>"); } public static MvcHtmlString CheckListBox(this HtmlHelper htmlHelper, string name, IEnumerable<CheckListBoxItem> items, string itemFormat) { name = htmlHelper.Encode(name); var resultBuilder = new StringBuilder(); var itemList = items.ToList(); for (int index = 0; index < itemList.Count; index++) { CheckListBoxItem item = itemList[index]; string encodedKey = htmlHelper.Encode(item.Key); string encodedText = htmlHelper.Encode(item.Text); string keyHtml = string.Format("<input type=\"hidden\" name=\"{0}[{1}].Key\" value=\"{2}\"/>", name, encodedKey, encodedKey); string checkBoxHtml = string.Format( "<input type=\"checkbox\" name=\"{0}[{1}].Selected\" value=\"true\" {2} />", name, encodedKey, item.Selected ? "checked" : ""); resultBuilder.AppendFormat(itemFormat, keyHtml + checkBoxHtml, encodedText); } return MvcHtmlString.Create(resultBuilder.ToString()); } } } //CheckListBoxItemsModelBinder.cs using System.Linq; using System.Text.RegularExpressions; namespace System.Web.Mvc { public class CheckListBoxItemsModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var result = new CheckListBoxItems(); string modelName = bindingContext.ModelName; string regexKeyPattern = "^" + modelName + @"\[.+?\]\.Key$"; var keyRegex = new Regex(regexKeyPattern, RegexOptions.IgnoreCase); var keys = controllerContext.RequestContext.HttpContext.Request.Form.AllKeys.Where(x => keyRegex.IsMatch(x)); foreach (string key in keys) { var valueSubmittedForKey = bindingContext.ValueProvider.GetValue(key); bindingContext.ModelState.SetModelValue(key, valueSubmittedForKey); string valueKey = key.Substring(0, key.Length - 4) + ".Selected"; var valueSubmittedForValueKey = bindingContext.ValueProvider.GetValue(valueKey); bindingContext.ModelState.SetModelValue(valueKey, valueSubmittedForValueKey); var checkListBoxItem = new CheckListBoxItem(); result.Add(checkListBoxItem); checkListBoxItem.Key = valueSubmittedForKey.AttemptedValue; checkListBoxItem.Selected = valueSubmittedForValueKey != null; } return result; } } }

 

 

posted @ Friday, November 26, 2010 2:56 PM | Feedback (294) | Filed Under [ Code ]

Powered by: