I have an ASP.NET MVC 5 web application using the repository pattern, and I have several controllers that need to call my _loggingService
(queries audit logs) to get the last updated information for certain pages, not all pages.
I want to abstract this method into a common place, so that I don't have duplicated code in each controller where it is used.
I don't want to use the base controller, because I don't want to have to inject my service dependency into every controller, because it isn't used on a lot of controllers.
I don't want to make a common static class in my Application Layer, because I want to test it, and I want to avoid a static implementation.
I can't put it in another project, because it will create a circular reference to my Web Application Project because it is populating the LastUpdatedViewModel
, which is an object on several other viewmodels in the project.
How do I architect this?
HttpGet
for an EditPage
:
[HttpGet]
public virtual ActionResult Edit()
{
var settings = settingsService.GetSettingsForType(SettingTypeEnum.Application);
var viewModel = new AppSettingsEditViewModel
{
KeyBitLengthList = GetKeyBitLengthListItems(),
LastUpdatedViewModel = GetLastUpdated(Url.Action(MVC.ApplicationSettings.Edit(), Request.Url.Scheme, Request.Url.Host)),
.......
}
}
GetLastUpdated
is the method I want to extract. Here it is:
public LastUpdatedViewModel GetLastUpdated(string url)
{
var auditLog = _loggingService.GetMostRecentAudit(url);
if (auditLog != null)
{
var lastUpdatedViewModel = new LastUpdatedViewModel
{
LastUpdated = String.Format("{0} {1} {2}", "Last Updated:", auditLog.UserAccountEmail, auditLog.CreatedDate)
};
return lastUpdatedViewModel;
}
return null
}
BONUS POINTS for whomever can give me an elegant solution on how to get Request.Url.Scheme
and Request.Url.Host
mocked, and implemented in Unit Tests for our controllers, as we DO currently have unit tests on those. I'd also like to get this code tested and passing. Currently, my only solution to get tests passing was to just check for nulls on Request
and Request.Url
, and if either of those were null, return null in GetLastUpdated()
, like so:
private LastUpdatedViewModel GetLastUpdated()
{
if (Request == null) return null;
if (Request.Url == null) return null;
var url = Url.Action(MVC.EmailTemplate.EditSmtpSettings(), Request.Url.Scheme, Request.Url.Host);
var auditLog = _loggingService.GetMostRecentAudit(url);
if (auditLog != null)
{
var lastUpdatedViewModel = new LastUpdatedViewModel
{
LastUpdated = String.Format("{0} {1} {2}", "Last Updated:", auditLog.UserAccountEmail, auditLog.CreatedDate)
};
return lastUpdatedViewModel;
}
return null;
}
Which isn't a solution at all, only working code. What I need is a dynamic method that accepts a string URL parameter generated from a controller using T4MVC, that calls the service layer and builds my LastUpdatedViewModel
.
Here is a controller test that fails because Request
is null:
[Test] public void EditGET_ModalityKeyBitLengthSettingInList_ViewModelModalityKeyBitLengthEqual()
{
var appSettings = new List<Setting>
{
new Setting
{
Key = SettingsConstants.ModalityKeyBitLength,
Value = "1024"
}
};
AutoMocker.Get<ISettingsService>()
.Stub(x => x.GetSettingsForType(SettingTypeEnum.Application))
.Return(appSettings);
AutoMocker.ClassUnderTest.ControllerContext = MockControllerContext();
var result = AutoMocker.ClassUnderTest.Edit() as ViewResult;
var viewModel = result.Model as AppSettingsEditViewModel;
Assert.AreEqual(1024, viewModel.Settings.ModalityKeyBitLength);
}
protected ControllerContext MockControllerContext()
{
var context = MockRepository.GenerateStub<ControllerContext>();
var httpContext = MockRepository.GenerateStub<HttpContextBase>();
var session = MockRepository.GenerateStub<HttpSessionStateBase>();
httpContext.Stub(x => x.Session).Return(session);
httpContext.Expect(x => x.Cache).Return(HttpRuntime.Cache).Repeat.Any();
context.HttpContext = httpContext;
return context;
}
What I had done in another area of the application that used T4MVC was to just rip T4MVC out, and I added const strings in the service layer for URLs that we needed when we were building emails to send in our service layer.
Is T4MVC able to be implemented elegantly? The more I use it the more it just seems like a problem rather than a solution. But that's probably because I'm doing it wrong.