HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Web application using the repository pattern

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
theapplicationrepositoryusingwebpattern

Problem

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

Solution

Well, my first attempt is to suggest injecting a mapper into the controllers you want to do this for (assuming you are using a DI framework. If not anyone of the popular choices out there will meet your needs. AutoMapper, Unity, Autofaq etc).

public interface IMap 
    where TTarget: class
    where TSource: class
{
    TTarget Map(TSource source);
}

public interface ILoggingServiceAdapter : IMap
{
   // any other items you wish to expose from your logging service
}

public class LoggingServiceMapper : ILoggingServiceAdapter 
{   
    private readonly LoggingService _loggingService;
    public LoggingServiceMapper(LoggingService loggingService)
    {
        _loggingService = loggingService;
    }

    public LastUpdatedViewModel Map(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;
    }
}


Hence your methods would now look like

private readonly ILoggingServiceAdapter _loggingService;

public MyControllerConstructor(ILoggingServiceAdapter loggingService)
{
    _loggingService = loggingService;
}
[HttpGet]
public virtual ActionResult Edit()
{
    var settings = settingsService.GetSettingsForType(SettingTypeEnum.Application);

    var viewModel = new AppSettingsEditViewModel
    {
        KeyBitLengthList = GetKeyBitLengthListItems(),
        LastUpdatedViewModel = _loggingService.Map(Url.Action(
                                                     MVC.ApplicationSettings.Edit(),
                                                     Request.Url.Scheme,
                                                     Request.Url.Host)),
        .......
    }
}


Now I don't know about the BONUS option, but if you wanted to further eliminate the need
to always be specifying the Scheme and Host when Creating the action you could consider writing an extension class on UrlHelper.

public static class UrlHelperExtensions
{
    public static string Action(this UrlHelper urlHelper, string action, Uri uri) {
        return urlHelper.Action(action, uri.Scheme, uri.Host);
    }
}


Hence your code now becomes like:

[HttpGet]
public virtual ActionResult Edit()
{
    var settings = settingsService.GetSettingsForType(SettingTypeEnum.Application);

    var viewModel = new AppSettingsEditViewModel
    {
        KeyBitLengthList = GetKeyBitLengthListItems(),
        LastUpdatedViewModel = _loggingService.Map(Url.Action(
                                                     MVC.ApplicationSettings.Edit(),
                                                     Request.Url)),
        .......
    }
}

Code Snippets

public interface IMap<TTarget, TSource> 
    where TTarget: class
    where TSource: class
{
    TTarget Map(TSource source);
}

public interface ILoggingServiceAdapter : IMap<TTarget, TSource>
{
   // any other items you wish to expose from your logging service
}

public class LoggingServiceMapper : ILoggingServiceAdapter 
{   
    private readonly LoggingService _loggingService;
    public LoggingServiceMapper(LoggingService loggingService)
    {
        _loggingService = loggingService;
    }

    public LastUpdatedViewModel Map(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;
    }
}
private readonly ILoggingServiceAdapter _loggingService;

public MyControllerConstructor(ILoggingServiceAdapter loggingService)
{
    _loggingService = loggingService;
}
[HttpGet]
public virtual ActionResult Edit()
{
    var settings = settingsService.GetSettingsForType(SettingTypeEnum.Application);

    var viewModel = new AppSettingsEditViewModel
    {
        KeyBitLengthList = GetKeyBitLengthListItems(),
        LastUpdatedViewModel = _loggingService.Map(Url.Action(
                                                     MVC.ApplicationSettings.Edit(),
                                                     Request.Url.Scheme,
                                                     Request.Url.Host)),
        .......
    }
}
public static class UrlHelperExtensions
{
    public static string Action(this UrlHelper urlHelper, string action, Uri uri) {
        return urlHelper.Action(action, uri.Scheme, uri.Host);
    }
}
[HttpGet]
public virtual ActionResult Edit()
{
    var settings = settingsService.GetSettingsForType(SettingTypeEnum.Application);

    var viewModel = new AppSettingsEditViewModel
    {
        KeyBitLengthList = GetKeyBitLengthListItems(),
        LastUpdatedViewModel = _loggingService.Map(Url.Action(
                                                     MVC.ApplicationSettings.Edit(),
                                                     Request.Url)),
        .......
    }
}

Context

StackExchange Code Review Q#42995, answer score: 2

Revisions (0)

No revisions yet.