[NameAttribute] Workaround in WebApi HelpPage


[NameAttribute] Workaround in Microsoft.AspNet.WebApi.HelpPage

The Nuget package Microsoft.AspNet.WebApi.HelpPage is really helpful to automatically create the needed help for your web api. But, sometimes, depending on your solution structure, can be difficult to use it.

The package works really fine, smooth and unassisted when you have the standard MVC template in your web api project, but, what happen if we complicate it a bit? For instance, in a bigger solution, we might want to split the models in an independent project and, besides, we have the same name of a class but in a different namespace. Personally I prefer to have different name of classes for different purposes, even if they are in different namespaces, but, sometimes, you have to choose between adding prefixes of suffixes to the classes names, or having the same name.

Let’s do a small test. First of all, we create an MVC application with a WebAPI (or simply a Web API application, it does not matter for this article but I decided to start with this template to show how to add a Help Page from the scratch):

CreateProject

 

 

 

selectTemplates

 

 

 

 

 

 

 

 

If we run the application we can see the standard base application executing. Let’s add, to that application, a new ApiController with a model:

selectTemplates

 

 

 

 

 

 

 

 

And let’s name this controller “MoneyBanksController”. The template that will be created will be similar to this one:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace HelpPageErrorSimulator.Controllers
{
public class MoneyBanksController : ApiController
{

// GET: api/MoneyBanks
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}

// GET: api/MoneyBanks/5
public string Get(int id)
{
return "value";
}

// POST: api/MoneyBanks
public void Post([FromBody]string value)
{

}

// PUT: api/MoneyBanks/5
public void Put(int id, [FromBody]string value)
{

}

// DELETE: api/MoneyBanks/5
public void Delete(int id)
{

}

}

}

Now, let’s change the template “string” type by a new class named “Bank”. The result will be something like this:

{

// GET: api/MoneyBanks
public IEnumerable<Bank> Get()
{
return new Bank[] { new Bank(), new Bank() };
}

// GET: api/MoneyBanks/5
public string Get(int id)
{
return "value";
}

// POST: api/MoneyBanks
public void Post([FromBody]Bank value)
{

}

// PUT: api/MoneyBanks/5
public void Put(int id, [FromBody]Bank value)
{

}

// DELETE: api/MoneyBanks/5
public void Delete(int id)
{

}

}

Of course, as we didn’t write the Bank class yet, it appears in red. Let’s create the class inside the Models folder:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace HelpPageErrorSimulator.Models
{

public class Bank
{

public string Name { get; set; }

public string Special1 { get; set; }

public string Special2 { get; set; }

}

}

Now we can go back to the MoneyBanks controller and add a reference to the namespace containing the Bank class:

using HelpPageErrorSimulator.Models;

Now we can add the HelpPages nuget package. To do that, open the Package Manager Console and write this command:

PM> install-package Microsoft.AspNet.WebApi.HelpPage

If we run the application now, we are able to navigate to the url “/help” and we will see the help page for the controller we wrote. Pretty simple, huh?

Browser con API

 

 

 

 

 

 

Let’s complicate the system a bit. In a big application, you should have your models in an external project, that way, if you have a N-layer architecture, you can use the models everywhere. So, let’s do it. We create a new project in the solution of the class “Windows Desktop/Class lIbrary” and we call it MoneyBanks.Models.

After that, we can delete the created class in the new project and cut and paste our Bank class to this project. The only thing we have to do, is to fix the namespace name in the class. It should end like this:

namespace MoneyBanks.Models
{

public class Bank
{

public string Name { get; set; }

public string Special1 { get; set; }

public string Special2 { get; set; }

}

}

Of course, now we have to reference this new project from the web application and then, add a reference to that namespace in the MoneyBanks ApiController:

using MoneyBanks.Models;

If we run the application now, we won’t see any difference with the all-in-one template application we had before.

Now we are going to force a problem.

Let’s say, our application is about banks, but about different type of banks, we already created the webapi for the money banks, and now we want to create a new webapi for blood banks.

Let’s start adding a controller for that. As we did previously, no difference but the name used. This time, we are going to call this controller “BloodBanksController”.

Let’s change the class used in this controller. If you compare this controller with the other one, they look the same…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace HelpPageErrorSimulator.Controllers
{

public class BloodBanksController : ApiController
{

// GET: api/BloodBanks
public IEnumerable&lt;Bank&gt; Get()
{
return new Bank[] { new Bank(), new Bank() };
}

// GET: api/BloodBanks/5
public string Get(int id)
{
return "value";
}

// POST: api/BloodBanks
public void Post([FromBody]Bank value)
{

}

// PUT: api/BloodBanks/5
public void Put(int id, [FromBody]Bank value)
{

}

// DELETE: api/BloodBanks/5
public void Delete(int id)
{

}

}

}

As before, we have in red the inexistent class, so, let’s create it. To do that, we are going to create a new class library project called BloodBanks.Models. Then, we changed the automatically created class1.cs by this code:

namespace BloodBanks.Models
{

public class Bank
{

public string Name { get; set; }

public string Special3 { get; set; }

public string Special4 { get; set; }

}

}

As you can see, the only difference with the “other” Bank class, is the namespace and a couple of properties with different name. Now we can go back to the controller project, add a reference to this new project, and add a new using class to the controller:

using System.Collections.Generic;
using System.Web.Http;
using BloodBanks.Models;
namespace HelpPageErrorSimulator.Controllers
{

public class BloodBanksController : ApiController
{

// GET: api/BloodBanks
public IEnumerable&lt;Bank&gt; Get()
{
return new Bank[] { new Bank(), new Bank() };
}

// GET: api/BloodBanks/5
public string Get(int id)
{
return "value";
}

// POST: api/BloodBanks
public void Post([FromBody]Bank value)
{

}

// PUT: api/BloodBanks/5
public void Put(int id, [FromBody]Bank value)
{

}

// DELETE: api/BloodBanks/5
public void Delete(int id)
{

}

}

}

Now run the application in the help page.

HelpPage with 2 apis

 

 

 

 

 

 

 

 

Everything seems to be working fine, right? Well, click on any method and it should raise an exception (A model description could not be created. Duplicate model name ‘Bank’ was found for types ‘MoneyBanks.Models.Bank’ and ‘BloodBanks.Models.Bank’. Use the [ModelName] attribute to change the model name for at least one of the types so that it has a unique name.):

Error

 

 

 

 

 

 

The problem is that the help page creator is finding a collision between the two classes that have the same name.

The Solutions

First Solution

What the error is telling you, is that you need to use the [ModelName] attribute in, at least, one of the classes, naming it differently to avoid the collision. Well, we can do it in our solution, but it won’t work. Let’s try, let’s modify, for instance, the first Bank class to add that attribute:

namespace MoneyBanks.Models
{

[ModelName("MoneyBank")]
public class Bank
{

public string Name { get; set; }

public string Special1 { get; set; }

public string Special2 { get; set; }

}

}

Well, it will never find the ModelName attribute, because it’s implementation is inside the web application project, inside the help area, so, if we reference that project, it will cause a circular reference problem.

We can avoid it by creating a new project in the solution. The target of this new project is to have the help pages module inside.

So, let’s create a new class library project called HelpPageErrorSimulator.HelpArea. Once it is created, let’s make some surgery to the Web project and the new project:

  • Create a folder in the HelpPageErrorSimulator.HelpArea called Areas.
  • Create a folder in the HelpPageErrorSimulator.HelpArea, inside the Areas folder, called HelpPage.
  • Move everything in the Web project under the folder Areas/HelpPage to the recent created folder of the secondary project, EXCEPT the Views folder and the HelpPage.css file.

If we try to build the solution now, we will see that obviously we need to add some references to the new project, those are:

System.ComponentModel.DataAnnotations

System.Runtime.Serialization

System.Web

System.Web.Helpers

System.Web.Http

System.Net

System.Net.Http

System.Net.Http.Formatting

Then, we have to add these packages to the new project, as we did before, using the Package Manager Console:

Install-Package Microsoft.AspNet.Mvc -Version 5.2.3

Install-Package Microsoft.AspNet.WebApi.Client

Install-Package Microsoft.AspNet.WebApi.Core

Install-Package Newtonsoft.Json -Version 6.0.8

Install-Package Microsoft.AspNet.WebApi.WebHost -version 5.2.3

After adding all these references, we will be able to build the new project. Now, in order to have all working, we need to add a reference to this new project in our web application and a reference to this new project to the MoneyBanks.Models project.

Then, edit the MoneyBanks.Models.Bank class and add a using clause, it will end like this:

using HelpPageErrorSimulator.Areas.HelpPage.ModelDescriptions;
namespace MoneyBanks.Models
{

[ModelName("MoneyBank")]
public class Bank
{

public string Name { get; set; }

public string Special1 { get; set; }

public string Special2 { get; set; }

}

}

As you can see, the ModelName attribute is not in red, so, we have done things well.

It’s time to run the application again.

If you click on the first method link of the MoneyBank API, now you can see that the help is working and it shows you a link to your “Named” model MoneyBank, instead of the real name of the class.

You can see the full project from GitHub here:

https://github.com/victorxata/HelpPageErrorSimulator/tree/master

Second Solution

What the error is telling you, is that you need to use the [ModelName] attribute in, at least, one of the classes, naming it differently to avoid the collision. If you do it, as we show in the first solution, you will see a different name of the Bank class in your help. It might not be what you want, as the name of the class may be important. I think that the best solution is to add to the help system, the entire namespace of the class, this way, we don’t have the same name in different classes and, then, we avoid the problem.

Also, with this solution, we don’t need to add the ModelName attribute to any class.

To do it, we need to change some classes in the original help system to use, instead of the name of the class, the FULLNAME of the class that has the complete namespace.

We need to change this classes:

ModelNameHelper

HelpPageSampleGenerator

Replace, in ModelNameHelper, the content of the class with this:

using System;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace HelpPageErrorSimulator.Areas.HelpPage.ModelDescriptions
{

internal static class ModelNameHelper
{
// Modify this to provide custom model name mapping.

public static string GetModelName(Type type)
{
ModelNameAttribute modelNameAttribute = type.GetCustomAttribute&lt;ModelNameAttribute&gt;();
if (modelNameAttribute != null &amp;&amp; !String.IsNullOrEmpty(modelNameAttribute.Name))
{
return modelNameAttribute.Name;
}

string modelName = type.FullName;

if (type.IsGenericType)
{
// Format the generic type name to something like: GenericOfAgurment1AndArgument2
Type genericType = type.GetGenericTypeDefinition();
Type[] genericArguments = type.GetGenericArguments();
string genericTypeName = genericType.FullName;
// Trim the generic parameter counts from the name
genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
string[] argumentTypeNames = genericArguments.Select(t =&gt; GetModelName(t)).ToArray();
modelName = String.Format(CultureInfo.InvariantCulture, "{0}Of{1}", genericTypeName, String.Join("And", argumentTypeNames));

}

return modelName;

}

}

}

As you can see if you compare, the changes I’ve made are simple, I changed the type.Name by type.FullName and genericType.Name by genericType.FullName (not really necessary this last one).

This way, instead of getting the name of the class, the system will get the full name, including the namespace.

Replace, in HelpPageSampleGenerator, the method WriteSampleObjectUsingFormatter with this one:

[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as InvalidSample.")]
public virtual object WriteSampleObjectUsingFormatter(MediaTypeFormatter formatter, object value, Type type, MediaTypeHeaderValue mediaType)
{

if (formatter == null)
{
throw new ArgumentNullException("formatter");
}

if (mediaType == null)
{
throw new ArgumentNullException("mediaType");
}

object sample = String.Empty;
MemoryStream ms = null;
HttpContent content = null;

try
{
if (formatter.CanWriteType(type))
{
ms = new MemoryStream();
content = new ObjectContent(type, value, formatter, mediaType);
formatter.WriteToStreamAsync(type, value, ms, content, null).Wait();
ms.Position = 0;
StreamReader reader = new StreamReader(ms);
string serializedSampleString = reader.ReadToEnd();
if (mediaType.MediaType.ToUpperInvariant().Contains("XML"))
{
serializedSampleString = TryFormatXml(serializedSampleString);
}
else if (mediaType.MediaType.ToUpperInvariant().Contains("JSON"))
{
serializedSampleString = TryFormatJson(serializedSampleString);
}
sample = new TextSample(serializedSampleString);
}
else
{
sample = new InvalidSample(String.Format(
CultureInfo.CurrentCulture,
"Failed to generate the sample for media type '{0}'. Cannot use formatter '{1}' to write type '{2}'.",
mediaType,
formatter.GetType().FullName,
type.FullName));
}
}
catch (Exception e)
{
sample = new InvalidSample(String.Format(
CultureInfo.CurrentCulture,
"An exception has occurred while using the formatter '{0}' to generate sample for media type '{1}'. Exception message: {2}",
formatter.GetType().FullName,
mediaType.MediaType,
UnwrapException(e).Message));
}
finally
{
if (ms != null)
{
ms.Dispose();
}
if (content != null)
{
content.Dispose();
}
}

return sample;
}

If you take a look at the code, I have done exactly the same here, instead of formatter.GetType().Name, I’ve written formatter.GetType().FullName and instead of type.Name I’ve written type.FullName.

After done that, you can run the application showing the help page and, if you click on a link, you will see, instead of the name of the class, the full namespace of the class:

Full namespace

 

 

 

 

 

 

 

And that is everything. Now the help system will work even with classes with the same name in different namespaces.

You can see the full project from GitHub here:

https://github.com/victorxata/HelpPageErrorSimulator2/tree/Master1

I hope this helps you in your projects.

Anuncios

Localize Web Applications with Microsoft MVC


Localize Web Application with Microsoft MVC

Demo Solution Structure

Solution Structure

Modifications required to localize the application

ModificationsRequired

Presentation

As you can see, this is a standard MVC4 web application. To make it localizable, we have to take these actions.

Right click on the project name, click on Add, click on Add ASP.NET Folder, click on App_GlobalResources, this is required to make the MVC engine think it has global resources.

This action is just for create the folder. Leave it empty.

Modify the file RouteConfig.cs to add support for languages in the QueryString parameters. Leave like this:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, lang = "en" }
);
}

We are telling route engine that, if there is not a language in the route, it has to add “en” by default. Otherwise, it has to add the supplied QueryString Param to the route.

We will come back to this project below. Leave it at this moment and let’s go to the Infrastructure layer.

Infrastructure.CrossCutting.Localization

In that layer, there is a project called Infrastructure.CrossCutting.Localization

LocalizationProject

As you can see, in there, there are several classes and 3 resx files. In these resx files we have to write all required strings.

ResourceFiles

The important thing here is to put the Resource Access Modifier to Public as shown.

After that, if you click on the Global (can be of the resx files in the project, and press F4 to see its properties, you can see these properties:

CodeGenerator

The important thing here is that the Build Action is selected as Embedded Resource and the Custom Tool is PublicResXFileCodeGenerator (which is selected when you put Access Modifier to Public at the last point). Another thing to see here is the Custom Tool Namespace used to make it directly readable by the MVC4 application without adding anything else. This is the namespace that will contain the auto generated code with the strings.

The next thing that we have to look at is the CultureHelpers class. This simple static class, only have one static method. This method is the responsible of changing the current culture on the current thread (as it is a web application the thread is regenerated between requests). This is the method we have to invoke to change language. We can see this invocation below.

The next class to read is the LocalizedDisplayName class. This class inherits from the DisplayNameAttribute to override the DisplayName attribute used in the models of the MVC4 application.

public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string _resourceName;
public LocalizedDisplayNameAttribute(string resourceName)
{
_resourceName = resourceName;
}
public override string DisplayName
{
get { return Global.ResourceManager.GetString(_resourceName); }
}
}

Thanks to this override, where we were using

[Display(Name = "User name")]

Now we can use

[LocalizedDisplayName("USERNAME")]

And the application will retrieve the translation from the Global resx file based on the current culture.

Putting all together

You can see an example of this behavior on the view Account/Login. The definition of the Login Model, has changed to this (you can see it in Models/AccountModels):

public class LoginModel
{

[Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "REQUIRED")]
[LocalizedDisplayName("USERNAME")]
public string UserName { get; set; }

[Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "REQUIRED")]
[DataType(DataType.Password)]
[LocalizedDisplayName("PASSWORD")]
public string Password { get; set; }

[LocalizedDisplayName("REMEMBERME")]
public bool RememberMe { get; set; }

}

Note that in the new model class, we are using the Global Resources to translate both DisplayName and the Required Error Message.

We do not have to do anything else in the view, because with the razor helpers, the application will get the correct translation based on the current culture:

<ol>
<li>
@Html.LabelFor(m => m.UserName)
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
@Html.LabelFor(m => m.Password)
@Html.PasswordFor(m => m.Password)
@Html.ValidationMessageFor(m => m.Password)
</li>
<li>
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe, new { @class = "checkbox" })
</li>
</ol>
<input type="submit" value="@Global.LOGIN" />

Note that we are introducing translations also in the cshtml file directly from the Global resources, right in the value of the input button. To achieve this, look at the first line of the view, it is using the Resources Namespace:

@using Resources

Finally, we have a class called SetCultureFilter. It is a class that inherits from ActionFilterAttribute. We will override the method OnActionExecuting to set the culture from the QueryString Params:

public class SetCultureFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var culture = (string) filterContext.RouteData.Values["lang"];
CultureHelpers.SetCulture(culture);
base.OnActionExecuting(filterContext);
}
}

The job is almost done. We only have to add the attribute to the Controllers that we want to be auto translated and it will works. For example, if you open HomeController, you can see below the class, the attributed we created in the Localization dll:

[SetCultureFilter]
public class HomeController : Controller

This is telling the MVC4 engine to execute the specified culture filter for every action on the Controller (although we can restrict the filter and apply only to selected actions if required).

If you see the HomeController, you can see an action called ChangeLanguage. Well this action is a quick way to change the current language:

public ActionResult ChangeLanguage(int id)
{
switch (id)
{
case 1:
#if DEBUG
CultureHelpers.SetCulture("es"); // Duplicated code due to be used in unit tests as filter not executing in test, to be investigated
#endif
_currentCulture = "es";
break;
default:
#if DEBUG
CultureHelpers.SetCulture("en");
#endif
_currentCulture = "en";
break;
}
return RedirectToAction("Index", new {lang = _currentCulture});
}

Evidently, the duplicated code is not best way to do this, it is written this way just for the prototype.

To execute this action, you can open the shared view _LoginPartial. We had added to this list, two more items:

<li>@Html.ActionLink(@Global.LANGENGLISH, "ChangeLanguage", "Home", new { id = 0 }, null)</li>
<li>@Html.ActionLink(@Global.LANGSPANISH, "ChangeLanguage", "Home", new { id = 1 }, null)</li>

These two items execute the language change, and the method on the controller redirects to the home page.

Tests Driven

Additionally, you can see on the Tests layer, a couple of projects that show how to test the controllers (integration tests) and how to test the method (unit tests).

Conclusion:

After done all this actions, you can simply use

on the views the syntax @Global.KEY,

on the C# classes the syntax Global.KEY and

on the data models the syntax [LocalizedDisplayName(“KEY”)

or, i.e. [Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = “KEY”)]

to retrieve a localized string.

Take a look also at _Layout.cshtml, it has this line:

<p>&copy; @DateTime.Now.ToShortDateString() – Translation Example</p>

At the footer. Well, if you run the application in Spanish, to demonstrate date format changes the format shown is dd/mm/yyyy but if you run the application in English, the format shown is mm/dd/yyyy. Validation of dates on the client side would require some more investigation.

You can download the source project here: https://github.com/victorxata/TranslatedWeb/.

Invalidar OutputCache en una aplicación MVC3


Ya sabemos que para activar el OutputCache  en MVC3, simplemente hay que decorar el Action Result que queramos con el ActionFilter OutputCache, por ejemplo:

[OutputCache(CacheProfile="Render")]
public ActionResult AllProducts()
{

Bien, esta configuración hará que el motor mvc busque la configuración del caché en el web config, en el que tendremos, por ejemplo, ésta:

<system.web>
   <caching>
      <outputCacheSettings>
         <outputCacheProfiles>
            <add name="Render" duration="3600" location="Any" />
         </outputCacheProfiles>
      </outputCacheSettings>
   </caching>
</system.web>
<system.webServer>
   <staticContent>
      <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00"/>
   </staticContent>
   <caching>
      <profiles>
        <add extension=".swf" location="Any" duration="1.00:00:00" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod"/>
        <add extension=".css" location="Any" duration="1.00:00:00" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod"/>
        <add extension=".png" location="Any" duration="1.00:00:00" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod"/>
        <add extension=".gif" location="Any" duration="1.00:00:00" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod"/>
        <add extension=".jpg" location="Any" duration="1.00:00:00" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod"/>
        <add extension=".js" location="Any" duration="1.00:00:00" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod"/>
      </profiles>
   </caching>
</system.webServer>

Si modificamos el resultado a obtener en nuestro Action, es fácil que queramos invalidar el caché para que vuelva a cargar el contenido desde la base de datos y si además, nuestro contenido aparece en varios sitios del site, no nos valdrá con invalidar una URL relativa, acción que realizaríamos simplemente con:

HttpResponse.RemoveOutputCacheItem("/Products/AllProducts");

Si es el caso y queremos invalidar toda la caché, podemos hacerlo en una sola sentencia. Para ello, tenemos que crear una dependencia. Es decir, haremos que toda caché de todo ActionResult, dependan de un objeto, de este modo, al reiniciar dicho objeto, reniciaremos la caché dependiente.

Manos a la obra.

Primer punto, modificamos el global.asax para crear el objeto de caché con vida infinita (hasta que lo reiniciemos, como hemos dicho antes). Para ello, modificamos el método Application_Start y le añadimos la creación de dicho objeto:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    HttpContext.Current.Cache.Insert("Pages", DateTime.Now, null,
        System.DateTime.MaxValue, System.TimeSpan.Zero,
        System.Web.Caching.CacheItemPriority.NotRemovable,
        null);
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Segundo punto, creamos una clase que herede de ActionFilterAttribute para generar el Filtro:

public class CacheDependencyFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            HttpContext.Current.Response.AddCacheItemDependency("Pages");
        }
    }

Tercer punto, modificamos el global.asax para añadir el filtro creado y que la dependencia se genere cada vez que se ejecuta un Action. Para ello, modificamos el método RegisterGlobalFilters:

public void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new CacheDependencyFilter());
}

Con esto ya tenemos nuestra infraestructura creada. Lo último que nos quedaría por hacer, es un método que nos sirva para reiniciar (invalidar) el caché. Para ello, podemos, por ejemplo crear una clase con un método estático que realice dicho cometido:

public class CacheFactory
{
    public static void InvalidateCache()
    {
        HttpContext.Current.Cache.Insert("Pages", DateTime.Now, null,
            System.DateTime.MaxValue, System.TimeSpan.Zero,
            System.Web.Caching.CacheItemPriority.NotRemovable,
            null);
    }
}

De este modo, en cualquier punto de nuestra aplicación, podremos invalidar el caché simplemente invocando a:

CacheFactory.InvalidateCache();

Y bien, esto es todo por hoy.

Google
Visita mi perfil en Google Plus