Bei ASP.NET MVC ist es Best Practice wenn man für den Datenbank Zugriff eine Transaction beim Start der Action öffnet und zum Ende wieder schließt.
Ich habe die eine oder andere “Lösung” dazu gesehen, die sich jedoch meist in den Request-LifeCycle der Application einklinken und somit “irgendwie” außerhalb der MVC Konzeptes sind und somit auch beim Zugriff auf andere Seiten oder gar statischen Resourcen auch Connection/Transactionen öffnen. Auch ich nutzte dies … aber nun nicht mehr.
Die beste Lösung (jedenfalls aus meiner Sicht) ist ein eigener IActionInvoker der diese Aufgabe übernimm,. damit ist auch exakt den Gültigkeitsbereich der Transaktion definiert.
public interface IActionInvoker
{
bool InvokeAction(ControllerContext controllerContext, string actionName);
}
In ASP.NET MVC ist es ein ActionInvoker der dafür sorgt dass die
Action und das
ActionResult so verarbeitet werden wie wir es vom Framework kennen. Die Standardimplementierung ist der
ControllerActionInvoker. Der Controller selbst übernimmt die Erstellung des
IActionInvoker und zwar über die Methode
CreateActionInvoker(). Diese ist virtuell und kann somit angepasst werden.
protected virtual IActionInvoker CreateActionInvoker()
{
return new ControllerActionInvoker();
}
Somit zu meiner Lösung. Ich habe einen IActionInvoker implementiert, der dafür sorge trägt dass der Aufruf der Action in in einer Datenbank-Transaction abläuft.
public class TransactionalActionInvoker : IActionInvoker
{
private readonly IDatabaseTransaction transaction;
private readonly IActionInvoker baseActionInvoker;
public TransactionalActionInvoker(IDatabaseTransaction transaction,
IActionInvoker baseActionInvoker)
{
this.transaction = transaction;
this.baseActionInvoker = baseActionInvoker;
}
public bool InvokeAction(ControllerContext controllerContext, string actionName)
{
bool result;
try
{
transaction.BeginTransaction();
result = baseActionInvoker.InvokeAction(controllerContext, actionName);
}
catch (Exception)
{
transaction.ExceptionWhileTransaction();
throw;
}
finally
{
transaction.EndTransaction();
}
return result;
}
}
Als Abhängigkeiten drücke ich dem TransactionActionInvoker den eigentlichen
IActionInvoker rein sowie eine IDatabaseTransaction zum Management des Transaktion. Ich gehe diesen Weg und leite nicht von
ControllerActionInvoker ab damit beliebige Implementierungen von vorhandenen ActionInvokern verwendet werden können.
IDatabaseConnection ist ein eigenes Interface, darüber kann man die Transaction öffnen, und wieder schließen sowie mitteilen ob es im Ablauf der Action ein Problem gab. Da die Implementierung Datenbankabhängig ist gehe ich hier nicht Groß darauf ein, nur der Hinweis das bei EndTransaction() bei einem Problem die Transaction mit einem Rollback() beendet wird sonst mit einem Commit().
public interface IDatabaseTransaction
{
void BeginTransaction();
void EndTransaction();
void ExceptionWhileTransaction();
}
In meinen Anwendungen habe ich immer einen Basis-Controller von dem ich alle meine Controller ableite. Ich hoffe Du machst dies auch so! Somit ist es sehr enfach die Erzeugung des
IActionInvoker auszutauschen und den eigenen zu verwenden.
public abstract class TransactionController : Controller
{
public IDatabaseTransaction Transaction { get; set; }
protected override IActionInvoker CreateActionInvoker()
{
return new TransactionalActionInvoker(Transaction,base.CreateActionInvoker());
}
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
Transaction.ExceptionWhileTransaction();
}
}
Nicht viel besonderes, es wird eine Instanz vom TransactionActionInvoker erzeugt und ihr die IDatabaseTransaction sowie der ursprüngliche IActionInvoker übergeben.
Wichtig ist es OnException() zu überschreiben um der IDatabaseTransaction mitzuteilen das eine Exception innerhalb der Action aufgetreten ist.
Die IDatabaseTransaction muss natürlich auch irgendwie in den Controller kommen, dies wird in diesem Fall vom IoC-Container gemacht und zwar per Property-Injection. Ich habe hier der Property-Injection gegenüber der Konstruktor Injection den Vorzug gegeben damit ich die die Abhängigkeit nicht bei jedem zu implementierenden Controller “manuell” bedienen muss. Dies kann man jedoch machen wie man möchte, ein Lookup über den Container oder IServiceLocator wäre auch möglich.
Den Lebenszyklus von IDatabaseTransaction habe ich über den IoC-Container auf pro HttpRequest festgelegt. Dies ist erforderlich, auch ist es erforderlich dass die eigenen Datenbank Session/Connection auf pro HttpRequest festgelegt ist. Es muss sichergestellt werden dass alle Datenbank Aufrufe innerhalb der Action mit derselben Connection bzw. Session aufgerufen werden.
Print | posted on Saturday, August 07, 2010 2:08 PM
Der Eintrag ist mir etwas Wert