I have 2 attributes:
SecuredOperationAttribute
ExceptionPolicyAttribute
If user doesn't has an access to the action on controller then I throw an custom NonAuthorizedException but I can't catch it on ExceptionPolicyAttribute
My code:
[LogMethod]
[ExceptionPolicy]
public ActionResult Edit(int id)
{
// some works on here
}
[Serializable]
public class ExceptionPolicyAttribute : OnExceptionAspect
{
private ILog logger;
private string methodName;
public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
this.methodName = method.DeclaringType.FullName + "." + method.Name;
}
public override void OnException(MethodExecutionArgs args)
{
Guid guid = Guid.NewGuid();
var stringBuilder = new StringBuilder(1024);
// Write the exit message.
stringBuilder.Append(this.methodName);
stringBuilder.Append('(');
// Write the current instance object, unless the method
// is static.
object instance = args.Instance;
if (instance != null)
{
stringBuilder.Append("this=");
stringBuilder.Append(instance);
if (args.Arguments.Count > 0)
stringBuilder.Append("; ");
}
// Write the list of all arguments.
for (int i = 0; i < args.Arguments.Count; i++)
{
if (i > 0)
stringBuilder.Append(", ");
stringBuilder.Append(args.Arguments.GetArgument(i) ?? "null");
}
// Write the exception message.
stringBuilder.AppendFormat("): Exception ");
stringBuilder.Append(args.Exception.GetType().Name);
stringBuilder.Append(": ");
stringBuilder.Append(args.Exception.Message);
logger.Error(stringBuilder.ToString(), args.Exception);
args.FlowBehavior = FlowBehavior.Continue;
}
public override Type GetExceptionType(System.Reflection.MethodBase targetMethod)
{
return typeof(NonAuthorizedException);
}
}
And the secure attribute is:
[Serializable]
public class SecuredOperationAttribute: OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
IUserManager userManager = new UserManager();
int userId = userManager.GetUserIdFromCookie;
AdminUser adminUser = GenericSessionHelper<AdminUser>.Get(userId.ToString(), State.Session);
if(!User.CanAccess)
{
args.ReturnValue = null;
throw new NonAuthorizedException(string.Format("{0} userId li kullanıcının {1} işlemini yapmak için yetkisi yoktur",userId,args.Method.Name));
}
return;
}
}
What could be a problem? Am I using postsharp in a wrong way?
I found the solution:
I was using attributes as like :
[SecuredOperation]
[ExceptionPolicy]
public ActionResult Edit(int id)
but ExceptionPolicy couldn't catch exception. so I moved the ExceptionPolicy to top of the Controller Class:
[ExceptionPolicy]
public class UserController : BaseAuthorizedUserController
now it works.
Related
In the below code snippet, I am trying to mock the response of a protected method and test the response of the public method which calls the protected method. The protected method calls an external API, hence I would like to mock the response in order to test the public method which calls the protected method.
The problem arises when I try to inject the dependency on the class which is used by the public method. It always goes to the default constructor ignoring the dependency objects I pass. Please let me know where I am missing the change.
Note: I have added the default constructor just to understand the flow. I will not have it in the real implementation.
Test Method
[Fact]
public void Test()
{
using (var mock = AutoMock.GetLoose())
{
Depdency1 depdency1 = new Depdency1();
Depdency2 depdency2 = new Depdency2();
var parm1 = new NamedParameter("dependency1", depdency1);
var parm2 = new NamedParameter("dependency2", depdency2);
//Mock the protected method
mock.Mock<SystemUnderTest>(parm1, parm2)
.Protected()
.Setup<bool>("IsOrderValid", "TestOrder")
.Returns(true);
var sut = mock.Create<SystemUnderTest>();
sut.ProcessOrder("Test");
}
}
Main class
public class SystemUnderTest : ISystemUnderTest
{
private readonly IDependency1 _dependency1;
private readonly IDependency2 _dependency2;
public SystemUnderTest()
{
}
public SystemUnderTest(IDependency1 dependency1, IDependency2 dependency2)
{
_dependency1 = dependency1;
_dependency2 = dependency2;
}
public bool ProcessOrder(string OrderID)
{
//Businss logic using dependency1
if (IsOrderValid(OrderID))
{
if (_dependency1 == null)
{
throw new AggregateException("Depdency1 is null");
}
//Businss logic
return true;
}
else
{
if (_dependency1 == null)
{
throw new AggregateException("Depdency1 is null");
}
//Businss logic
return false;
}
}
protected virtual bool IsOrderValid(string OrderID)
{
//Business logic using dependency2
if (_dependency2 == null)
{
throw new AggregateException("Depdency2 is null");
}
return true; //False based on logic
}
}
public interface IDependency1
{
void Method1();
}
public interface IDependency2
{
void Method2();
}
public class Depdency1 : IDependency1
{
private int _property1;
public void Method1()
{
throw new NotImplementedException();
}
}
public class Depdency2 : IDependency2
{
private int _property2;
public void Method2()
{
throw new NotImplementedException();
}
}
I'm working on some legacy code (def: untested code - some well designed some not) and trying to develop some tests to confirm recent changes did what they expected etc. I'm running into an issue where I'm trying to force a method that has a try{catch} block in it to throw an exception using Moq. When I try to run the test it fails during the mock.Setup call with System.ArgumentException "Expression of type 'System.Web.Mvc.ActionResult' cannot be used for return type 'System.Web.Mvc.ActionResult'".
The basic setup of the code:
Interface for FilterController...
public interface IFilterController
{
ActionResult DeleteFilter(string reportFilter, bool customReport = true);
}
FilterController class...
public class FilterController : BaseController, IFilterController
{
public FilterController(
IServiceFactory serviceFactory,
IAwsServiceFactory awsServiceFactory,
IReportServiceFactory reportServiceFactory,
IAzureServiceFactory azureServiceFactory)
: base(typeof(FilterController), serviceFactory, awsServiceFactory, reportServiceFactory, azureServiceFactory)
{
}
// method under test
public ActionResult (string reportFilter, bool customReport = true) {
try {
// NOTE: I have trimmed down the actual code in the try block significantly for brevity - I should be able to hook onto something here as a way to mock something throwing an exception
var customReportFilterService = _serviceFactory.CreateCustomReportFilterService();
var emailReportSettingService = _serviceFactory.CreateEmailReportSettingService();
string message = string.Empty;
JsonReturnType type = JsonReturnType.DisplayMessage; // an enum
var filter = customReportFilterService.GetReportFilterByHash(SessionHelper.User.CustomerId, reportFilter, initLinkedProjects: true);
return JsonActionResult(type, ajaxMessage: message, redirectTo: filter == null ? null : string.Format("Report/{0}", filter.ReportName));
}
catch (Exception ex)
{
return JsonActionResult(JsonReturnType.Error, ajaxMessage: "There was an error in deleting the filter.");
}
}
}
BaseController class...
public class BaseController : Controller
{
private readonly ProgressController _progressController;
protected IServiceFactory _serviceFactory;
protected IAwsServiceFactory _awsServiceFactory;
protected IReportServiceFactory _reportServiceFactory;
protected IAzureServiceFactory _azureServiceFactory;
protected IApplicationSettingService _applicationSettingService;
protected IReportMonitorService _reportMonitorService;
protected ISymmetricAlgorithmProvider HiddenEncrypter { get; set; }
private Stopwatch _watch;
private bool _timePageEnabled;
private bool _maintenance;
private int _pageLoadThreshold;
private readonly ILog Logger;
public BaseController(Type type, IServiceFactory serviceFactory, IAwsServiceFactory awsServiceFactory, IReportServiceFactory reportServiceFactory, IAzureServiceFactory azureServiceFactory)
{
Logger = LogManager.GetLogger(type);
_progressController = new ProgressController();
_serviceFactory = serviceFactory;
_awsServiceFactory = awsServiceFactory;
_reportServiceFactory = reportServiceFactory;
_azureServiceFactory = azureServiceFactory;
_applicationSettingService = _serviceFactory.CreateApplicationSettingService();
_reportMonitorService = _serviceFactory.CreateReportMonitorService();
_watch = new Stopwatch();
_timePageEnabled = _applicationSettingService.ReadApplicationSettingFromCache<bool>(CC.Data.Model.Constants.ApplicationSettings.CheckSlowPageLoad, true);
_pageLoadThreshold = _applicationSettingService.ReadApplicationSettingFromCache<int>(CC.Data.Model.Constants.ApplicationSettings.PageLoadThreshold, 120);
_maintenance = _applicationSettingService.MaintenanceMode();
}
// System.Web.Mvc.ActionResult type mentioned in error
public ActionResult JsonActionResult(JsonReturnType returnType, string view = null, string ajaxMessage = null, string redirectTo = null, string target = null, object data = null, string popupTitle = null)
{
if (returnType == JsonReturnType.LoadContent)
_progressController.CompleteProgressCache();
var serializer = new JavaScriptSerializer();
serializer.MaxJsonLength = Int32.MaxValue;
var resultData = new {
ReturnType = returnType,
HtmlView = view,
Message = ajaxMessage,
RedirectTo = redirectTo,
Target = target,
CustomData = data,
ProjectId = SessionHelper.ProjectId,
PopupTitle = popupTitle,
MaintenanceMode = _maintenance
};
ContentResult result;
result = new ContentResult
{
Content = serializer.Serialize(resultData),
ContentType = "application/json"
};
return result;
}
}
Controller class...
public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter {
// stuff
}
Unit Test class...
[TestClass]
public class FilterControllerTest
{
private FilterController filterController;
private Mock<IFilterController> filterControllerMock;
private Mock<IServiceFactory> serviceFactoryMock;
private Mock<IAwsServiceFactory> awsServiceFactoryMock;
private Mock<IReportServiceFactory> reportServiceFactoryMock;
private Mock<IAzureServiceFactory> azureServiceFactoryMock;
private Mock<IApplicationSettingService> applicationSettingServiceMock;
[ClassInitialize]
public static void ClassInit(TestContext context)
{
}
[TestInitialize]
public void Initialize()
{
filterControllerMock = new Mock<IFilterController>();
serviceFactoryMock = new Mock<IServiceFactory>();
awsServiceFactoryMock = new Mock<IAwsServiceFactory>();
reportServiceFactoryMock = new Mock<IReportServiceFactory>();
azureServiceFactoryMock = new Mock<IAzureServiceFactory>();
applicationSettingServiceMock = new Mock<IApplicationSettingService>();
serviceFactoryMock
.Setup(s => s.CreateApplicationSettingService())
.Returns(applicationSettingServiceMock.Object);
filterController = new FilterController(
serviceFactoryMock.Object
, awsServiceFactoryMock.Object
, reportServiceFactoryMock.Object
, azureServiceFactoryMock.Object);
}
[TestCleanup]
public void Cleanup()
{
}
[ExpectedException(typeof(Exception))]
[TestMethod]
public void DeleteFilter_ExceptionThrown_IsCaughtAndLoggedAndReturnsActionResultOfError()
{
// Arrange
filterControllerMock
.Setup(x => x.DeleteFilter(It.IsAny<string>(), It.IsAny<bool>()))
.Throws(new Exception());
// Act
var result = filterController.DeleteFilter("myfilt", false);
}
}
In the end all I want to do is have it so when DeleteFilter is called for this test, an error is thrown and then I can assert what is returned from the catch block.
EDIT: have majorly updated the post by suggestion to make it easier to understand where the issue is.
Here is a slimmed down example
Given
public interface IServiceFactory {
object GetService(string args);
}
public class MyController : Controller {
IServiceFactory serviceFactory
public MyController(IServiceFactory serviceFactory) {
this.serviceFactory = serviceFactory;
}
// method under test
public ActionResult DeleteFilter(string args) {
try {
var model = serviceFactory.GetService(args);
return View(model);
} catch(Exception ex) {
return JsonActionResult(JsonReturnType.Error, ajaxMessage: "There was an error in deleting the filter.");
}
}
}
You can use moq in your test like this
[TestMethod]
public void DeleteFilter_ExceptionThrown_IsCaughtAndLoggedAndReturnsActionResultOfError() {
// Arrange
var serviceFactoryMock = new Mock<IServiceFactory>();
serviceFactoryMock
.Setup(x => x.GetService(It.IsAny<string>())
.Throws(new Exception())
.Verifiable();
var controller = new MyController(serviceFactoryMock.Object);
// Act
var result = controller.DeleteFilter("blah blah");
//Assert
serviceFactoryMock.Verify(); // verifies that the setup was invoked
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(JsonActionResult));
//...other assertions
}
So now in the example when DeleteFilter is called, the mock service factory is invoked, an error is thrown based on the setup and you can assert what is returned from the catch block.
I'm using fluentvalidation and lightinject
Here is my code to insert a blog article;
public OperationResultDto Add(BlogArticleDto blogArticleDto)
{
OperationResultDto result = new OperationResultDto();
ValidationResult validationResult =
_blogArticleModelValidator.Validate(blogArticleDto);
if (!validationResult.IsValid)
{
result.IsOk = false;
ValidationFailure firstValidationFailer =
validationResult.Errors.FirstOrDefault();
if (firstValidationFailer != null)
{
result.Message = firstValidationFailer.ErrorMessage;
}
return result;
}
BlogArticle blogArticle = new BlogArticle {
Title = blogArticleDto.Title,
ShortBody = blogArticleDto.ShortBody,
Body = blogArticleDto.Body,
IsOnline = blogArticleDto.IsOnline,
CategoryName = blogArticleDto.CategoryName,
PublishedBy = blogArticleDto.PublishedBy,
PublishDate = blogArticleDto.PublishDate,
Tags = new List<string>(), //TODO parse, model's tags in one string.
CreateDate = DateTime.Now,
MainPhotoPath = blogArticleDto.MainPhotoPath,
};
_blogArticleRepository.Add(blogArticle);
return result;
}
As you can see, "validation section" is huge and I don't want to validate my dto parameters in my service(business) layer. I want to validate "arguments" in my ioc (lightinject).
Here is my ioc code to proceed that;
public class ServiceInterceptor : IInterceptor
{
public object Invoke(IInvocationInfo invocationInfo)
{
Log.Instance.Debug("Class: ServiceInterceptor -> Method: Invoke started.");
string reflectedTypeFullname = String.Empty;
string methodName = String.Empty;
if (invocationInfo.Arguments.Any())
{
//TODO Validate method parameters here..
foreach (object argument in invocationInfo.Arguments)
{
}
}
if (invocationInfo.Method.ReflectedType != null)
{
reflectedTypeFullname = invocationInfo.Method.ReflectedType.FullName;
methodName = invocationInfo.Method.Name;
}
... ...
Now, I can take all arguments of a method to give them to my fluentvalidator. So I know I need to define typeOf argument here but after that how can I call fluent validation's related validation object* to validate argument ?
I am the author of LightInject and maybe you could see if this example works out for you.
class Program
{
static void Main(string[] args)
{
var container = new ServiceContainer();
container.Register<AbstractValidator<Foo>, FooValidator>();
container.Register<IFooService, FooService>();
container.Intercept(sr => sr.ServiceType.Name.EndsWith("Service"), factory => new ServiceInterceptior(factory));
var service = container.GetInstance<IFooService>();
service.Add(new Foo());
}
}
public interface IFooService
{
void Add(Foo foo);
}
public class FooService : IFooService
{
public void Add(Foo foo)
{
}
}
public class Foo
{
}
public class FooValidator : AbstractValidator<Foo>
{
}
public class ServiceInterceptior : IInterceptor
{
private readonly IServiceFactory factory;
public ServiceInterceptior(IServiceFactory factory)
{
this.factory = factory;
}
public object Invoke(IInvocationInfo invocationInfo)
{
foreach (var argument in invocationInfo.Arguments)
{
Type argumentType = argument.GetType();
Type validatorType = typeof (AbstractValidator<>).MakeGenericType(argumentType);
var validator = factory.TryGetInstance(validatorType);
if (validator != null)
{
var validateMethod = validatorType.GetMethod("Validate", new Type[] { argumentType });
var result = (ValidationResult)validateMethod.Invoke(validator, new object[] { argument });
if (!result.IsValid)
{
//Throw an exception, log or any other action
}
}
}
//if ok, proceed to the actual service.
return invocationInfo.Proceed();
}
}
I am trying to convert my Controller to use DI.
Based on the article here, I've now got this code:
namespace HandheldServer.Controllers
{
public class DuckbillsController : ApiController
{
static IDuckbillRepository _platypiRepository;
public DuckbillsController(IDuckbillRepository platypiRepository)
{
if (platypiRepository == null)
{
throw new ArgumentNullException("platypiRepository is null");
}
_platypiRepository = platypiRepository;
}
public int GetCountOfDuckbillRecords()
{
return _platypiRepository.Get();
}
public IEnumerable<Duckbill> GetBatchOfDuckbillsByStartingID(int ID, int CountToFetch)
{
return _platypiRepository.Get(ID, CountToFetch);
}
public void PostDuckbill(int accountid, string name)
{
_platypiRepository.PostDuckbill(accountid, name);
}
public HttpResponseMessage Post(Duckbill Duckbill)
{
Duckbill = _platypiRepository.Add(Duckbill);
var response = Request.CreateResponse<Duckbill>(HttpStatusCode.Created, Duckbill);
string uri = Url.Route(null, new { id = Duckbill.Id });
response.Headers.Location = new Uri(Request.RequestUri, uri);
return response;
}
}
}
...but it doesn't compile; I get, "Inconsistent accessibility: parameter type 'HandheldServer.Models.IDuckbillRepository' is less accessible than method 'HandheldServer.Controllers.DuckbillsController.DuckbillsController (HandheldServer.Models.IDuckbillRepository)'"
The interface parameter type mentioned in the err msg is:
using System.Collections.Generic;
namespace HandheldServer.Models
{
interface IDuckbillRepository
{
int Get();
IEnumerable<Duckbill> Get(int ID, int CountToFetch);
Duckbill Add(Duckbill item);
void Post(Duckbill dept);
void PostDuckbill(int accountid, string name);
void Put(Duckbill dept);
void Delete(int Id);
}
}
What do I need to do to get around this err msg?
You need to explicitly mark the interface as public:
public interface IDuckbillRepository
{
// ....
}
Also, don't mark it static in your Controller.
I have some controller actions that I want to have custom caching on. For example lets say I have a
controller action ActionResult Index(string name) {}. I want to cache on the server the HTML of this action, unless there is a "live=true" querystring parameter in the url. If that parameter is present I would like to remove that action result from the server cache and serve the response normally.
We use OutputCache(Location=OutputCacheLocation.Server) attribute to do our caching usually. Is it possible to extend this attribute somehow and make it clear the cache if the live=true parameter is present in the URL?
Are there alternative that I can use to accomplish this if I can't customize the OutputCache attribute to get the behavior I need?
UPDATE
Based on James feedback here is the code that I have:
public class LiveOutputCacheAttribute : OutputCacheAttribute
{
private const string _resetParam = "live";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var context = filterContext.HttpContext;
AddLiveToVaryByParam();
if (context.Request[_resetParam] == "true")
{
var urlToRemove = GetUrlToRemove(filterContext);
context.Response.RemoveOutputCacheItem(urlToRemove);
return;
}
base.OnActionExecuting(filterContext);
}
private void AddLiveToVaryByParam()
{
// add live reset flag when vary by param is specified
if (VaryByParam != "*" && !VaryByParam.Contains("live"))
VaryByParam = string.Format("{0};{1}",VaryByParam, _resetParam).TrimStart(';');
}
private static string GetUrlToRemove(ActionExecutingContext filterContext)
{
var routeValues = new RouteValueDictionary(filterContext.ActionParameters);
var urlHelper = new UrlHelper(filterContext.RequestContext);
string action = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
return urlHelper.Action(action, controller, routeValues);
}
}
Here is how I use this on my action:
[LiveOutputCache(Location = OutputCacheLocation.Server, Duration = 60 * 60, VaryByParam = "name")]
public ActionResult Index(string name)
{
ViewData.Model = name + "-----" + DateTime.Now.Ticks.ToString();
return View();
}
The problem is that when I use the live=true parameter, it is still not removing the original request from the cache. Am I doing something wrong here?
You could use the VaryByParam attribute to check whether the live option is true e.g.
public class LiveOutputCacheAttribute : OutputCacheAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (VaryByParam == "true")
{
// clear cache
return;
}
base.OnActionExecuting(filterContext);
}
}
...
[LiveOutputCache(Location=OutputCacheLocation.Server, VaryByParam="live")]
public ActionResult Index(string name)
{
...
}
See How to programmatically clear outputcache for controller action method for the clearing part of it.
You can't customize OutputCacheAttribute to get the behaviour, but you could write your own CustomCacheAttribute to achive this. To do this you can get sources of OutputCacheAttribute (MVC is opensource so you can do it), copy it and rewrite function OnActionExecuting(ActionExecutingContext filterContext).
Check out my blog post on creating your own Custom Output Cache in ASP.NET MVC http://bstavroulakis.com/blog/web/custom-output-caching-in-asp-net-mvc/
I had the following expectations from the output cache which weren't met
1) Have the ability to view the Caching Object when necessary and all of its children to invalidate parts when needed.
2) Have the ability to disable caching when needed.
3) Have some logic before and after the item was cached.
4) Have some parts dynamic on the site and load only those parts, having the rest of the site static
5) Use the cache structure in other parts of the site as well.
My actions where:
To create my own CacheManager that will add/remove/find/... objects
in the cache.
public class CacheManager
{
#region ICacheManager Members
public static void Add(string key, object value, int expireSeconds)
{
if (expireSeconds == CacheManagerKey.CacheLifeSpanForever)
WebCache.Add(key, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
else
WebCache.Add(key, value, null, DateTime.MaxValue, TimeSpan.FromSeconds(expireSeconds), CacheItemPriority.Normal, null);
}
public static bool Contains(string key)
{
return WebCache.Get(key) != null;
}
public static int Count()
{
return WebCache.Count;
}
public static void Insert(string key, object value)
{
WebCache.Insert(key, value);
}
public static T Get(string key)
{
return (T)WebCache.Get(key);
}
public static List GetCacheKeys()
{
List keys = new List();
foreach (DictionaryEntry entry in HttpContext.Current.Cache) keys.Add(entry.Key.ToString());
return keys;
}
public static void Remove(string key)
{
WebCache.Remove(key);
}
public static void RemoveAll()
{
List keys = GetCacheKeys();
foreach (string key in keys)
WebCache.Remove(key);
}
public object this[string key]
{
get
{
return WebCache[key];
}
set
{
WebCache[key] = value;
}
}
#endregion
public static System.Web.Caching.Cache WebCache
{
get
{
System.Web.Caching.Cache cache = null;
if (HttpContext.Current != null)
cache = HttpContext.Current.Cache;
if (cache == null)
cache = HttpRuntime.Cache;
return cache;
}
}
}
After that I created my own attribute
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
public class WebCacheAttribute : ActionFilterAttribute
{
public int Duration { get; set; }
public string CacheKey { get; set; }
public Dictionary CacheParams { get; set; }
public Type CacheReturnType { get; set; }
public string ContentType { get; set; }
public HeaderContentTypeEnum ResponseHeaderContentType{get;set;}
public string CacheObj { get; set; }
private readonly ICacheHoleFiller _cacheHoleFiller;
public WebCacheAttribute(int duration, string cacheKey, string cacheParamsStr, HeaderContentTypeEnum response = HeaderContentTypeEnum.Html, Type type = null)
{
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public T GetCachedParam(Dictionary parameters, bool isAjaxRequest)
{
}
public string GetUniqueKey(bool isAjaxRequest)
{
}
public void OnException(ExceptionContext filterContext)
{
}
private HtmlTextWriter tw;
private StringWriter sw;
private StringBuilder sb;
private HttpWriter output;
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
To have some parts dynamic I used "Donut Caching"
Lastly, I created a cachehelper to call other methods in my project that will use the webcache attribute as well.
var articleStr = CacheHelper.InvokeCacheMethod(typeof(HtmlHelperExtensions), "RenderArticlesCallback", new object[] { (int)articleType });
[WebCacheAttribute(CacheManagerKey.CacheLifeSpanForever, CacheManagerKey.Page_Article_Key, "articleTypeID")]
public static string RenderArticlesCallback(int articleTypeID)
{
public static class CacheHelper
{
public delegate object SourceDataDelegate(object[] args);
public static T InvokeCacheMethod(Type type, string methodName, object[] args)
{
return (T)InvokeCacheMethod(type, methodName, null, args);
}
public static T InvokeCacheMethod(Type type, string methodName, object instance, object[] args)
{
var method = type.GetMethod(methodName);
var webCache = method.ReturnParameter.Member.GetCustomAttributes(typeof(WebCacheAttribute), true).FirstOrDefault();
Dictionary cacheParameters = FixCacheParameters(method, args);
T cachedObj;
if (Config.CacheEnabled && webCache != null)
{
cachedObj = ((WebCacheAttribute)webCache).GetCachedParam(cacheParameters, false);
if (cachedObj != null)
return cachedObj;
}
T returnObj = (T)method.Invoke(instance, args);
SaveCachedData(webCache, returnObj);
return returnObj;
}
public static void SaveCachedData(object webCache, object returnObj)
{
if (Config.CacheEnabled && webCache != null)
{
var fullParamString = ((WebCacheAttribute)webCache).GetUniqueKey(false);
CacheManager.Add(fullParamString, returnObj, ((WebCacheAttribute)webCache).Duration);
}
}
public static Dictionary FixCacheParameters(MethodInfo method, object[] args)
{
Dictionary cacheParameters = new Dictionary();
if (args != null)
{
var arguments = args.ToList();
var count = 0;
var argsCount = args.Length;
var methodParameters = method.GetParameters().ToList();
foreach (var argument in args)
{
var key = methodParameters[count].Name;
object value = null;
if (argsCount > count)
value = args[count];
if (value != null && value.GetType() == typeof(string))
value = (object)value.ToString();
if (value != null)
cacheParameters.Add(key, value);
count++;
}
}
return cacheParameters;
}
}
For more details on all of this you can visit my blog post => Custom Output Caching in ASP.NET MVC