Caching ASP.NET Web API with CacheCow - c#

I am trying to implement caching using CacheCow. I have two problems:
In some cases I need to invalidate manually the cache of some resources.
For example, I have a resource that it is called purchase, and other that is called pointMovements. They are not totally connected, but doing a post in purchase, implies some changes in pointMovement. Cachecow is not detecting these changes because I am not calling the API of pointmovements. So when I call the endpoint of pointmovements, the values are cached and I cannot get the new values.
To solve this, I need to invalidate that manually, how is that possible?
There are some controllers that I don't want to cache. I am trying to use attributes for doing that but it is not working. I am following this article but the attributes are ignored.
How can I specify which controllers to cache?

I came across the same set of problems and found a solution for problem 2 (disable caching regardless of the default settings).
// This forces the server to not provide any caching by refreshing its cache table immediately (0 sec)
[HttpCacheRefreshPolicy(0)]
// This forces the client (browser) to not cache any data returned from the server (even if ETag is present) by setting the time-out to 0 and no-cache to true.
[HttpCacheControlPolicy(true, 0, true)]
public void MyController : ApiControler {... }
The attributes must be applied together for this to work. You can also control the caching at the action level by providing the same rules to each action.
I've still to figure out the solution for problem 1. but watch this space for updates.
Update
I have found a solution to problem 1.
Register the CachingHandler with your IoC container (in my case it's IUnityContainer)
Inject the ICachingHandler into your Web API controller.
To invalidate the resource, use ICachingHandler.InvalidateResource(HttpRequestMessage)
Please see a code example below. The solution has been tested.
public class Bootstrapper
{
//...
// Create a new caching handler and register it with the container.
public void RegisterCache(HttpConfiguration config, IUnityContainer container)
{
var cachingHandler = new CachingHandler(config);
// ...
container.RegisterInstance<ICachingHandler>(cachingHandler);
}
}
public class ResourceContoller : ApiController
{
private ICachingHandler _cachingHandler;
public ResourceContoller(ICachingHandler cachingHandler)
{
_cachingHandler = cachingHandler;
}
[HttpPost]
public void DeleteResource(int resourceId)
{
// Do the delete
// ...
// Now invalidate the related resource cache entry
// Construct a http request message to the related resource
// HINT: The "DefaultApi" may not be your api route name, so change this to match your route.
// GOTCHA: The route matching mechanism is case sensitive, so be aware!
var relatedResource = new HttpRequestMessage(HttpMethod.Get, Url.Link("DefaultApi", new {controller = "linkedresource", action = "getlinkedresource", id: resourceId}));
// Invalidate the resource with the caching handler.
_cachingHandler.InvalidateResource(relatedResource);
}
}

Sorry for the late response.
As #Tri Q said, the way to do this is to use attributes which I have explained in this blog:
http://byterot.blogspot.co.uk/2013/03/rest-asp-net-wep-api-0.4-new-features-breaking-change-cachecow-server.html

I solved your #1 question using below code. Here I extend the IRoutePatternProvider interface. Remember, what you return in GetRoutePattern should match what you return in GetLinkedRoutePatterns. Only then the adding and removing will work. Try it out.
Inside Application_Start
CachingHandler cacheHandler = new CachingHandler(GlobalConfiguration.Configuration);
cacheHandler.RoutePatternProvider = new CacheRoutePatternProvider();
GlobalConfiguration.Configuration.MessageHandlers.Add(cacheHandler);
Custom Class
public class CacheRoutePatternProvider : IRoutePatternProvider
{
public string GetRoutePattern(HttpRequestMessage request)
{
string path = request.RequestUri.AbsolutePath;
if (!path.EndsWith("/"))
path += "/";
return path;
}
public IEnumerable<string> GetLinkedRoutePatterns(HttpRequestMessage request)
{
string path = request.RequestUri.AbsolutePath;
if(!path.EndsWith("/"))
path += "/";
int segmentIndex;
// return each segment of the resource heirarchy
while ((segmentIndex = path.LastIndexOf("/")) > 0)
{
path = path.Substring(0, segmentIndex);
if(path.Contains("/api/"))
yield return path + "/";
}
yield break;
}
}

Related

Sub domain as query string

Is there any way in ASP.net C# to treat sub-domain as query string?
I mean if the user typed london.example.com then I can read that he is after london data and run a query based on that. example.com does not currently have any sub-domains.
This is a DNS problem more than an C#/ASP.Net/IIS problem. In theory, you could use a wildcard DNS record. In practice, you run into this problem from the link:
The exact rules for when a wild card will match are specified in RFC 1034, but the rules are neither intuitive nor clearly specified. This has resulted in incompatible implementations and unexpected results when they are used.
So you can try it, but it's not likely to end well. Moreover, you can fiddle with things until it works in your testing environment, but that won't be able to guarantee things go well for the general public. You'll likely do much better choosing a good DNS provider with an API, and writing code to use the API to keep individual DNS entries in sync. You can also set up your own public DNS server, though I strongly recommend using a well-known and reputable commercial DNS host.
An additional problem you can run into is the TLS/SSL certificate (because of course you're gonna use HTTPS. Right? RIGHT!?) You can try a wild card certificate and probably be okay, but depending on what else you do you may find it's not adequate; suddenly you're needing to provision a separate SSL certificate for every city entry in your database, and that can be a real pain, even via the Let's Encrypt service.
If you do try it, IIS is easily capable of mapping the requests to your ASP.Net app based on a wildcard host name, and ASP.Net itself is easily capable of reading and parsing the host name out of the request and returning different results based on that. IIS URL re-writing should be able to help with this, though I'm not sure whether you can do stock MVC routing in C#/ASP.Net based on this attribute.
I have to add to the previous answers, that after you fix the dns, and translate the subdomain to some parameters you can use the RewritePath to move that parameters to your pages.
For example let say that a function PathTranslate(), translate the london.example.com to example.com/default.aspx?Town=1
Then you use the RewritePath to keep the sub-domain and at the same time send your parameters to your page.
string sThePathToReWrite = PathTranslate();
if (sThePathToReWrite != null){
HttpContext.Current.RewritePath(sThePathToReWrite, false);
}
string PathTranslate()
{
string sCurrentPath = HttpContext.Current.Request.Path;
string sCurrentHost = HttpContext.Current.Request.Url.Host;
//... lot of code ...
return strTranslatedUrl
}
A low tech solution can be like this: (reference: https://www.pavey.me/2016/03/aspnet-c-extracting-parts-of-url.html)
public static List<string> SubDomains(this HttpRequest Request)
{
// variables
string[] requestArray = Request.Host().Split(".".ToCharArray());
var subDomains = new List<string>();
// make sure this is not an ip address
if (Request.IsIPAddress())
{
return subDomains;
}
// make sure we have all the parts necessary
if (requestArray == null)
{
return subDomains;
}
// last part is the tld (e.g. .com)
// second to last part is the domain (e.g. mydomain)
// the remaining parts are the sub-domain(s)
if (requestArray.Length > 2)
{
for (int i = 0; i <= requestArray.Length - 3; i++)
{
subDomains.Add(requestArray[i]);
}
}
// return
return subDomains;
}
// e.g. www
public static string SubDomain(this HttpRequest Request)
{
if (Request.SubDomains().Count > 0)
{
// handle cases where multiple sub-domains (e.g. dev.www)
return Request.SubDomains().Last();
}
else
{
// handle cases where no sub-domains
return string.Empty;
}
}
// e.g. azurewebsites.net
public static string Domain(this HttpRequest Request)
{
// variables
string[] requestArray = Request.Host().Split(".".ToCharArray());
// make sure this is not an ip address
if (Request.IsIPAddress())
{
return string.Empty;
}
// special case for localhost
if (Request.IsLocalHost())
{
return Request.Host().ToLower();
}
// make sure we have all the parts necessary
if (requestArray == null)
{
return string.Empty;
}
// make sure we have all the parts necessary
if (requestArray.Length > 1)
{
return $"{requestArray[requestArray.Length - 2]}.{requestArray[requestArray.Length - 1]}";
}
// return empty string
return string.Empty;
}
Following question is similar to yours:
Using the subdomain as a parameter

Is that possible ? GeckoFX can use seperate CookieContainer per instance?

I'm Using Geckfx22.0 and xulrunner22.0. Since GeckoWebBrowser in .Net shares cookies with all other instances of GeckoWebBrowsers I would like for a GeckoWebBrowser to have it's own cookie container which doesn't share any cookies that was created previously in other GeckoWebBrowsers or other instances.
For example when I create a GeckoWebBrowser it shouldn't have any cookies. And when I run 2 instances of GeckoWebBrowser they have their own cookie container and don't share or conflict cookies with each other.
How is that possible?
I've tried various possible ways by creating different class and initiating geckofx but when running different browser at same time it sharing cookies among other browsers. If i remove cookies from one browser , the same happening for other browsers too. I have initiated the proxy and useragent at different times and its works but cant apply various useragents for multiple browsers at the same time.
public void Initiate()
{
Gecko.Xpcom.Initialize(AppDomain.CurrentDomain.BaseDirectory + "/xulrunner");
if (this.IsProxySet)
{
Gecko.GeckoPreferences.User["network.proxy.http"] = this.Host;
Gecko.GeckoPreferences.User["network.proxy.http_port"] = this.Port;
Gecko.GeckoPreferences.User["network.proxy.type"] = 1;
}
if (IsUseragentSet)
{
Gecko.GeckoPreferences.User["general.useragent.override"] = this.Useragent;
}
}
And to remove cookies i'm using following code :
nsICookieManager CookieMan;
CookieMan = Xpcom.GetService<nsICookieManager>("#mozilla.org/cookiemanager;1");
CookieMan = Xpcom.QueryInterface<nsICookieManager>(CookieMan);
CookieMan.RemoveAll();
Help will be appreciated !!!
You could possibly try implementing your own cookie manager that supports this:
see unittest Register_AfterDefaultFactoryHasBeenUnregistered_NewCookieServiceIsUsedInsteadOfDefaultOne
for an example of how to do this.
This code is currently untested and may contain typeos
This code requires a geckofx version newer than v22.0-0.6
[Guid("c375fa80-150f-11d6-a618-0010a401eb10")]
[ContractID(TestCookieServiceFactory.ContractID)]
public class TestCookieServiceFactory
: GenericOneClassNsFactory<TestCookieServiceFactory, TestCookieService>
{
public const string ContractID = "#mozilla.org/cookieService;1";
}
public class TestCookieService : nsICookieService
{
// Implement nsICookieService...
}
public void Main()
{
Xpcom.Initialize("My Xulrunner/Fireofox location");
var existingFactoryDetails = TestCookieServiceFactory.Unregister();
TestCookieServiceFactory.Register();
var browser = new GeckofxWebBrowser();
// Add browser to form etc...
browser.Navigate("http://SomeWebPageThatUsesCookies")
// Cookie requests should now be sent to TestCookieService, process them as your want.
}

How to make this Unit Testable

Below I have some code that that I cannot Unit test because it tries to read settings from IIS7 and unfortunately our nightly build machine does not have IIS7. The only thing I can think of is to pass the ServerManager into the method, but then again in the caller I will have a ServerManager that will make that method unable to be unit tested. We use MOQ for our Mock library.
public ISection GetCurrentSettings(string location, Action<string> status)
{
#region Sanity Checks
if (string.IsNullOrEmpty(location))
{
throw new ArgumentNullException("location");
}
if (status == null)
{
throw new ArgumentNullException("status");
}
#endregion
ISection section = null;
_logger.Debug(string.Format("Retrieving current IIS settings for app at {0}.", location));
status("Getting current IIS settings.");
using (ServerManager manager = new ServerManager())
{
var data = (from site in manager.Sites
from app in site.Applications
from vdir in app.VirtualDirectories
where vdir.PhysicalPath.Equals(location, StringComparison.CurrentCultureIgnoreCase)
select new {Website = site, App = app}).SingleOrDefault();
if (data == null)
{
_logger.Debug(string.Format("Could not find an application at {0} in IIS. Going to load the defaults instead.", location));
//ToDo possibly load defaults
}
else
{
_logger.Debug(string.Format("Application found in IIS with website: {0} and a path of {1}", data.Website.Name, data.App.Path));
int port =
data.Website.Bindings.Where(b => b.EndPoint != null).Select(b => b.EndPoint.Port).Single();
section = new IISSection
{
ApplicationPoolName = data.App.ApplicationPoolName,
VirtualDirectoryAlias = data.App.Path,
WebsiteName = data.Website.Name,
WebsiteRoot = data.App.VirtualDirectories[0].PhysicalPath,
Port = port.ToString(CultureInfo.InvariantCulture),
WillApply = true,
AnonymousUser = _userService.GetUserByType(UserType.Anonymous)
};
}
return section;
}
Without rewriting your code fully, the general idea would be to pass in an ISettingReader* (implemented as IisSettingReader), which would expose methods that would get the data you need from IIS. Then, you can stub in the ISettingReader to return what you need, by passing ISettingReader into the method/class
*Or, IServerManager as it seems to be the current name, but I am not sure if that is IIS specific
UPDATE
To be more specific, as Darin Dimitrov elaborated, you need to pull all of the dependencies outside of the method and pass them in via parameter/constructor/property injection. This will require a rewrite of the code as it stands in its current state.
If not (and I do suggest a rewrite), then you can use something like TypeMock, which supposedly can fake the dependencies INSIDE a class, but I have not used this myself and only know what I have read on it.
Use Moq.
This will allow you to create a mocked version of ISettings rather than having to create a real one. It has the added advantage of allowing you to specify your own functionality as well.

Store the cache data locally

I develops a C# Winform application, it is a client and connect to web service to get data. The data returned by webservice is a DataTable. Client will display it on a DataGridView.
My problem is that: Client will take more time to get all data from server (web service is not local with client). So I must to use a thread to get data. This is my model:
Client create a thread to get data -> thread complete and send event to client -> client display data on datagridview on a form.
However, when user closes the form, user can open this form in another time, and client must get data again. This solution will cause the client slowly.
So, I think about a cached data:
Client <---get/add/edit/delete---> Cached Data ---get/add/edit/delete--->Server (web service)
Please give me some suggestions.
Example: cached data should be developed in another application which is same host with client? Or cached data is running in client.
Please give me some techniques to implement this solution.
If having any examples, please give me.
Thanks.
UPDATE : Hello everyone, maybe you think my problem so far. I only want to cache data in client's lifetime. I think cache data should be stored in memory. And when client want to get data, it will check from cache.
If you're using C# 2.0 and you're prepared to ship System.Web as a dependency, then you can use the ASP.NET cache:
using System.Web;
using System.Web.Caching;
Cache webCache;
webCache = HttpContext.Current.Cache;
// See if there's a cached item already
cachedObject = webCache.Get("MyCacheItem");
if (cachedObject == null)
{
// If there's nothing in the cache, call the web service to get a new item
webServiceResult = new Object();
// Cache the web service result for five minutes
webCache.Add("MyCacheItem", webServiceResult, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
else
{
// Item already in the cache - cast it to the right type
webServiceResult = (object)cachedObject;
}
If you're not prepared to ship System.Web, then you might want to take a look at the Enterprise Library Caching block.
If you're on .NET 4.0, however, caching has been pushed into the System.Runtime.Caching namespace. To use this, you'll need to add a reference to System.Runtime.Caching, and then your code will look something like this:
using System.Runtime.Caching;
MemoryCache cache;
object cachedObject;
object webServiceResult;
cache = new MemoryCache("StackOverflow");
cachedObject = cache.Get("MyCacheItem");
if (cachedObject == null)
{
// Call the web service
webServiceResult = new Object();
cache.Add("MyCacheItem", webServiceResult, DateTime.Now.AddMinutes(5));
}
else
{
webServiceResult = (object)cachedObject;
}
All these caches run in-process to the client. Because your data is coming from a web service, as Adam says, you're going to have difficulty determining the freshness of the data - you'll have to make a judgement call on how often the data changes and how long you cache the data for.
Do you have the ability to make changes/add to the webservice?
If you can Sync Services may be an option for you. You can define which tables are syncronised, and all the sync stuff is managed for you.
Check out
http://msdn.microsoft.com/en-us/sync/default.aspx
and shout if you need more information.
You might try the Enterprise Library's Caching Application Block. It's easy to use, stores in memory and, if you ever need to later, it supports adding a backup location for persisting beyond the life of the application (such as to a database, isolated storage, file, etc.) and even encryption too.
Use EntLib 3.1 if you're stuck with .NET 2.0. There's not much new (for caching, at least) in the newer EntLibs aside from better customization support.
Identify which objects you would like to serialize, and cache to isolated storage. Specify the level of data isolation you would like (application level, user level, etc).
Example:
You could create a generic serializer, a very basic sample would look like this:
public class SampleDataSerializer
{
public static void Deserialize<T>(out T data, Stream stm)
{
var xs = new XmlSerializer(typeof(T));
data = (T)xs.Deserialize(stm);
}
public static void Serialize<T>(T data, Stream stm)
{
try
{
var xs = new XmlSerializer(typeof(T));
xs.Serialize(stm, data);
}
catch (Exception e)
{
throw;
}
}
}
Note that you probably should put in some overloads to the Serialize and Deserialize methods to accomodate readers, or any other types you are actually using in your app (e.g., XmlDocuments, etc).
The operation to save to IsolatedStorage can be handled by a utility class (example below):
public class SampleIsolatedStorageManager : IDisposable
{
private string filename;
private string directoryname;
IsolatedStorageFile isf;
public SampleIsolatedStorageManager()
{
filename = string.Empty;
directoryname = string.Empty;
// create an ISF scoped to domain user...
isf = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain,
typeof(System.Security.Policy.Url), typeof(System.Security.Policy.Url));
}
public void Save<T>(T parm)
{
using (IsolatedStorageFileStream stm = GetStreamByStoredType<T>(FileMode.Create))
{
SampleDataSerializer.Serialize<T>(parm, stm);
}
}
public T Restore<T>() where T : new()
{
try
{
if (GetFileNameByType<T>().Length > 0)
{
T result = new T();
using (IsolatedStorageFileStream stm = GetStreamByStoredType<T>(FileMode.Open))
{
SampleDataSerializer.Deserialize<T>(out result, stm);
}
return result;
}
else
{
return default(T);
}
}
catch
{
try
{
Clear<T>();
}
catch
{
}
return default(T);
}
}
public void Clear<T>()
{
if (isf.GetFileNames(GetFileNameByType<T>()).Length > 0)
{
isf.DeleteFile(GetFileNameByType<T>());
}
}
private string GetFileNameByType<T>()
{
return typeof(T).Name + ".cache";
}
private IsolatedStorageFileStream GetStreamByStoredType<T>(FileMode mode)
{
var stm = new IsolatedStorageFileStream(GetFileNameByType<T>(), mode, isf);
return stm;
}
#region IDisposable Members
public void Dispose()
{
isf.Close();
}
}
Finally, remember to add the following using clauses:
using System.IO;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
The actual code to use the classes above could look like this:
var myClass = new MyClass();
myClass.name = "something";
using (var mgr = new SampleIsolatedStorageManager())
{
mgr.Save<MyClass>(myClass);
}
This will save the instance you specify to be saved to the isolated storage. To retrieve the instance, simply call:
using (var mgr = new SampleIsolatedStorageManager())
{
mgr.Restore<MyClass>();
}
Note: the sample I've provided only supports one serialized instance per type. I'm not sure if you need more than that. Make whatever modifications you need to support further functionalities.
HTH!
You can serialise the DataTable to file:
http://forums.asp.net/t/1441971.aspx
Your only concern then is deciding when the cache has gone stale. Perhaps timestamp the file?
In our implementation every row in the database has a last-updated timestamp. Every time our client application accesses a table we select the latest last-updated timestamp from the cache and send that value to the server. The server responds with all the rows that have newer timestamps.

Why is System.Data.Services.MimeTypeAttribute now only a class level attribute?

I'm getting started with Astoria/ADO.NET Data Services/WCF Data Services. Looking through a lot of the code samples out there, it appears that the MimeType attribute used to be a method level attribute. After installing the latest update, it is now a class level attribute.
If I have more than one Service Operation that I want to return as a certain MimeType, then it appears now that I have to create a new service for each operation. Is this correct?
Most examples are like this:
[WebGet]
[SingleResult]
[MimeType("application/pdf")]
public IQueryable<byte[]> FooPDF()
{
var result = from p in this.CurrentDataSource.MyPDFs
where p.FooID == 2
select p;
return result.Take(1).Select(p => p.PDF);
}
I get "Attribute 'MimeType' is not valid on this declaration type. It is only valid on 'class' declarations." when I compile, because now I can't do this.
Now, I have to do this:
[MimeType("FooPDF", "application/pdf")]
public class FooService : DataService<FooDBEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetServiceOperationAccessRule("FooPDF", ServiceOperationRights.All);
}
[WebGet]
[SingleResult]
public IQueryable<byte[]> FooPDF()
{
var result = from p in this.CurrentDataSource.MyPDFs
where p.FooID == 2
select p;
return result.Take(1).Select(p => p.PDF);
}
}
What's worse is that I can't add duplicate MimeType attributes to my class.
Is all of this really by design, or am I missing something?
Thanks for reporting this bug to us. I have opened this at our end to track this issue
With the recent update, we added the support for blobs as a first class concept in the data services. If you have a blob association with an entity, then both server and client have ways to surface this. To know more about this, please refer to the following link: http://msdn.microsoft.com/en-us/library/ee473426(v=VS.100).aspx
Hope this helps.
Thanks
Pratik
[MSFT]

Categories

Resources