Scaling ASP.NET Core Apps with NCache

Recorded webinar
By Ron Hussain and Zack Khan

ASP.NET is a very popular technology for developing web applications. Now its latest version ASP.NET Core is quickly gaining popularity with organizations anxious to move their web apps to ASP.NET Core. But, ASP.NET Core runs into scalability and performance issues when put under heavy user load. This happens because your data storage becomes a bottleneck. However, there is a way around this performance and scalability bottleneck if you use distributed caching. Learn how you can optimize your ASP.NET Core performance and scalability to perform under extreme transaction loads.

Here is what this webinar covers:

  • Overview of ASP.NET Core scalability and performance bottlenecks
  • How distributed caching resolves these issues
  • App Data Caching through ASP.NET Core IDistributedCache interface and Direct NCache APIs
  • ASP.NET Core Session Storage with NCache
  • ASP.NET Core Response Caching with NCache
  • Some important NCache distributed cache features

In today's webinar we're going to be doing an overview of ASP.NET Core scalability and performance bottlenecks and how distributed caching resolves those bottlenecks and other issues. We are going to talk about ASP.NET Core applications, what are their performance and scalability bottlenecks are and then we will talk about how to resolve those performance and scalability bottlenecks with the help of NCache distributed caching system.

NCache is an in-memory distributed caching system for your .NET and .NET Core applications. It's written in .NET and primarily for .NET and .NET Core applications but primarily today's focus is going to be around ASP.NET Core web apps and I have a few sample features available. I have lined up some sample applications as well. So, it's going to be a hands-on webinar where I will share some feature level details of different use cases that you can capture and features which are there to help address those use cases and I’ll demonstrate those samples as well. Let's get started.

So, our first few slides are introductory slides about ASP.NET Core platform in general. I'm pretty sure everybody knows what ASP.NET Core is. It's the new ASP web application platform. It's clean lightweight, modular. It's primarily MVC architecture, as far as, web application approach is concerned. You know, the popular front-end Angular, React it's mostly JavaScript based front-end and then MVC architecture to back it up. It's cross-platform. You can run it on Windows, Linux and any other operating system, such as macOS. And, typically it's something which is very compatible with legacy 4.0 ASP.NET applications, as well.

ASP.NET Core (Popular in High Traffic Apps)

If you have an ASP.NET MVC application, now you can migrate off of that and then start using ASP.NET Core technology for that matter, which is very fast, very robust and very lightweight and clean architecture, in general.

What is Scalability?

First thing that we'll talk about ASP.NET Core platform is it requires scalability. So, let's define what scalability is? Any application which is running under normal user load and by normalized I mean some users are there. Let's say 5 to 10 users are logged in and they're performing certain transactions, certain operations, read and write requests. Typically idea here is that your application would be super-fast under low user load. But, as soon as your application load grows and that's something which is a requirement for the business as well that you need to have more and more users logging on to your applications. That's when your applications tend to slow down. There could be slow performance under peak loads.

If your application is not able to maintain same performance that it had with low number of users and then when it has the high number of users if it is able to maintain the same performance, that application would be categorized as a very scalable application. The architecture has that ability to scale out and be able to handle the increased load from your end users. So, high performance under peak loads is what actually defines the scalability factor.

Here is a graph. Typically scalability is achieved within ASP.NET and ASP.NET Core platform through multiple Web or App Servers.

Linear Scalability

You can distribute your application load. You just deploy your application on multiple web or app servers and based on that you simply distribute your application user load onto those servers and as you add more servers you get a linear improvement in request handling capacity. More Servers mean more request handling capacity out of your application.

Here is a non-linear scalability where even after adding more Servers, the architecture is not designed in such a way that although you're adding a lot of Web Servers or App Servers but your applications are not scaling out, as your user load is growing.

Non-Linear Scalability

So, your transaction requirements are growing and you're adding Servers, you're doing all the right steps but you're still not able to gain the capacity that you need to have to accommodate the increased request load. So, that problem is typically associated with backend data sources and I’ll talk about it.

So, first of all let's see what applications need scalability. What are the different kinds of applications? Within ASP.NET & .NET Core we have Web Apps, and MVC Web Application, running the main business, some Web Services, some Service Calls, some Microservices, that's the new architecture and it could be any other general .NET or .NET Core Application.

All of these demand scalability, because you may have a lot of internal or external users or request load happening, which needs to be fulfilled by your application architecture.

The Scalability Problem

I mentioned that you can create a Web farm. You can distribute your application load on multiple servers. So, where exactly the scalability problem is? As you can see, if you add servers you should be able to handle more and more request load, because it gives you a linear scalability as far as App farm or Web farm is concerned.

But, when exactly you start seeing a non-linear scalability or no scalability at all? That is typically with relational databases. Your application tier scales out nicely, right. You can add more servers. You can create multiple instances of same application on the same box or you can have separate boxes hosting your applications. But all of these applications normally talk to a backend relational database and relational database is very good for storage but when it comes to handling extreme amount of request load, for example, you have a lot of transactional load happening on your applications, that database becomes a bottleneck. It's first of all going to slow things down, so, it's a performance bottleneck and then it does not have capacity to increase the scalability of it. It does not have any ability to increase capacity. It would just give you the capacity of the given server where the database is hosted.

It could also be some mainframe database. It could be some file system. All of these sources are a scalability bottleneck for your application and that's where your applications slow down. Your requests start to queue up and overall end user experience is impacted by that. NoSQL isn’t the answer. You know, I understand that a lot of NoSQL products are there. We also have a product called NosDB, which is a NoSQL database. But the main problem with NoSQL databases is that your applications would require a re-architecture, where you stop using a relational database and start using a NoSQL unstructured data source in comparison.

So, re-architecture of the application is not easy as well, right. So, that's another problem that you need to take. So, overall your applications are not scalable, primarily because of backend data sources.

The Solution: NCache Distributed Cache

What is the solution? It’s very simple, that you start using a distributed caching system like NCache. It's in-memory, so, it's super fast in comparison to database. So, that's the first benefit that you get out of it. So, with in-memory access, compared to disk based access NCache is super fast in comparison. So, first benefit, your regular application requests going to NCache in comparison to database, are going to be super-fast and it's going to take load off of the database as well by saving trips to the backend data sources. So, your database is free to do other things as well. Second benefit of it is, it's very scalable. It's linearly scalable in its model. It's not just a single server. There are multiple servers which you can use to host your cache cluster. It's a cache cluster. So, team of servers work in combination to one another and help serve your client requests. More servers mean more request handling capacity out of NCache and that's where it gives you huge benefits, where not only you get super-fast responses out of NCache, it's super-fast, low latency and it also gives you high throughput, extremely high scalability. You can keep adding more servers and you can linearly grow that capacity whenever your user load grows, without changing anything inside your application architecture.

And, nice thing about NCache is that you use it in addition to a backend database. It's not a replacement of your conventional relational data sources. We don't have to re-architecture the application to start using NCache. You can always use NCache in combination to a back-end data source, where some or most of your data is inside NCache and database has master copy of it. But, for some use cases, cache would have all the data. For example, for sessions and for other use cases.

NCache Scalability Numbers

Here are our throughput numbers. We conducted these tests in our AWS lab. This was a real life application data, simulated in our AWS lab. So, it's not a touch and go data. Actual application data simulated, in our environment. Where we used 5 NCache servers and we were able to achieve 2 Million requests per second out of NCache.

So, that's the kind of performance improvements you have if you or scalability numbers you have, if you have more and more servers added. So, 2 Million requests per second with just 5 NCache servers and it was a very linearly increasing trend and it should kept on increasing, if you keep on adding more servers.

These are published on our website as well. There's a video demonstration available as well.

Deployment Architecture

Here's our deployment architecture. Typically, this is how NCache distributed caching system is deployed. It is supported on all environments on-premise as well as in cloud. You can, you just need .NET or NET Core Framework to be available. That's the only prereq for NCache and you can deploy it on Windows as well as on Linux servers. It could be your physical or virtual boxes on-premise or it could be any public or private cloud, where you have .NET or .NET Core installed on Windows or Linux. So, these are two requirements.

Other than that NCache is supported on every platform. It's available in Azure Marketplace, as well as, AWS and we are also coming up with SaaS based model or deployment of NCache in our upcoming versions.

There are two deployment options. One is that you should use dedicated servers for caching and your applications connect in a client server model. That's the preferred model in cloud as well where you don't have client-side VMs but you do have server-side deployment.

And, the second deployment option is that you use your existing application boxes as your NCache servers as well. That's mostly on on-premise where you have application tier already there. Smaller configurations, you might want to use NCache to be installed on the same box, same set of servers.

In either way NCache handles most of your traffic. 100% is recommended and some traffic can always go to the backend database and this these could be your transactional operations read, write operations which you can handle through cache to back-end data sources.

So you can either use cache as site pattern where your application has data and updates data or fetches data from cache and the database in conjunction with one another or you can use read through and write through approach which is also available with NCache.

Common Uses of Distributed Cache

I'll talk about some use cases and then we'll jump right into our ASP.NET Core specific caching features. So, let's talk about some common use cases of NCache. There are many use cases. And, from a technical standpoint I have listed a few and these are specific to ASP.NET Core applications, but in general for any .NET or ASP.NET or ASP.NET Core or .NET Core. It could be a web application, some back-end application, some workflows, some Windows services, any application can utilize NCache and there are many use cases that you can choose from.

  1. App Data Caching

    Primarily, for ASP.NET Core, we will target App data caching and in this we have two options. You can use IDistributedCache interface. If you're already using IDistributedCache in your applications, you can simply plug in NCache as a provider for that. So, that is first option. Second option is that, you use direct NCache APIs and in App data caching you cache almost anything inside your application. It could be your domain objects, collections, data sets, any data that you would like to use more than once and you don't want to go to the backend database, you have retrieved that data and now you can preserve that data in the cache and you can keep using the cache repository and save trips to the database.

  2. ASP.NET Core Session Caching and SignalR Backplane

    Second use case is ASP.NET Core specific caching. These are our web front-end caching features. On this front, we have a lot of features. You can use ASP.NET Core session state. You know, it's a single-site and multi-site sessions. NCache is a distributed cache cluster. So, your sessions are stored in a distributed manner inside NCache. It's also a no code change option, almost with one line of code change in extension method where you plug in NCache. No extensive code changes are needed in order to use this and these are very reliable inside NCache because sessions are replicated across servers. So, any server going down, would not have any data loss or application downtime on your end.

    Second feature here is ASP.NET Core response caching. If you have static pages. Historically in ASP.NET we had output caching. In the newer ASP.NET Core platform, you can use the response caching, where you define page level headers and based on those all those pages page outputs are cached inside NCache. So, responses are cached. If you issue exact same requests again you get the pre-cached response made available. So, that's a feature within NCache, which is also a no code change option that you can use on ASP.NET Core front end caching level.

    And then, if you're using SignalR and if you have a web farm, you need a backplane as a must. So, SignalR Backplane is required if you go from a single server to a web farm deployment and in that case NCache can act as an ASP.NET Core Backplane, SignalR Backplane for your SignalR applications. It’s using Pub/Sub Messaging behind the scenes, which are event driven. Very fast, very scalable. So, you get benefits of high performance and scalability and high availability and reliability, if you plugin NCache as ASP.NET Core SignalR Backplane.

  3. Pub/Sub Messaging

    Finally, the third use case is Pub/Sub Messaging. Pub/Sub Messaging is a separate use case as well. For SignalR, behind the scenes we're using is Pub/Sub Messaging. But Pub/Sub Messaging can be used as a separate use case within NCache as well. For Microservices application this makes a lot of sense, where you have simple applications defined under Microservices architecture. Those are responsible for a dedicated task. A very important purpose that they are serving. So, it is very difficult to arrange communication between those Microservices and NCache can resolve that problem. You don't have to implement any communication within your Microservices applications yourself. You can rely on NCache communication platform for that and any application connected to it can send and receive messages and it doesn't have to have any performance degradation while they're doing it.

    You can have publisher subscriber model. Where publishers are publishing data to the cache and then cache is storing and relaying those messages to all the subscribers. It's an Async event driven mechanism. So, publisher and subscribers don't even have to know one another. So, there are no latency, no wait involved, no synchronous calls involved. So, it's very robust, very fast, loosely coupled architecture and everything is managed by NCache, as far as, this platform is concerned.

Hands-on Demo

So, we've covered these high level features. Next I’m going to show you the actual product in action. Where I'll create a cache cluster. Test it. Show you some monitoring aspects and then we'll talk about all these features that you can use in ASP.NET Core one by one. So, I have lined up some sample applications for that. So, I'm logging on to my demo environment.

So, here we have two NCache servers which I’ll be using and my box would act as a client machine from where I would run all these sample applications. So, idea here is that I'm going to use the web-based management tool and monitoring tool.

So, this web-based management tool allows you to manage and monitor everything from a single point. It could be any web request, http request from anywhere, from your environment to one of your NCache servers or clients and it should allow you to manage and monitor everything remotely through web.

Create a Clustered Cache

So, localhost, you can give an IP address and you can just access this resource from anywhere over the internet. So, I'm going to go ahead and create a new cache. Let's say, ‘aspcorecache’. All caches need to be named. You can create multiple cache clusters as well and you can come up with any meaningful cache name as needed.

Mode of serialization could be binary or JSON. So, I’m going to stick binary, stick to binary and then JSON is also very flexible, so, if you would like to use that and then I’ll pick partition of replica cache topology.

Within NCache there are many caching topologies. If I quickly take you to towards the end. So, we have many caching topologies to choose from. We have partitioned and then we have partitioning with backups. So, in this we have each server maintaining two partitions. An active data partition, where clients are connected and a passive replica partition of another server. As you can see serve one is active, it's backup is on two and server two is active, it's backup is on server one. In case any server goes down the backup gets activated and you get all the data from here. In under normal operations these partitions are having equal distribution of data.

Clients are connected to all the servers, so, read and write requests are super fast and if you add more servers you get more read and write request handling capacity because all those servers work in combination to one another. So, that's the topology I’m choosing partition of replica.

And then async replication option. Between active and backup, you can choose sync or async. Sync is more reliable. Async is faster. So, I'm going to go ahead with that.

Size 1024 is good enough for this demo but you can come up with any size that seems meaningful to you.

TCP parameters.

Question: We're currently using NCache, is there a way to avoid a client.ncc or nc conf file or something that you have in your development pipeline?

Yes. That's absolutely possible. client.ncconf allows you to specify configurations to connect to a cache server, where the cache cluster is running, right. In my case, I have two boxes and a cache name. So, client.ncconf has the cache name and the servers where this cache is configured. If you want to avoid client.ncconf, you can provide all these settings inline. We have cache initialization params. We call them cache init params. Those are that object allows you to specify all these configurations within the application code. So, yes. To answer that question, you need to use cache init params.

Question: Can we deploy NCache in Azure?

Absolutely. I discussed this when while we were sharing details about deployment architecture. NCache is fully supported in Microsoft Azure and AWS and any other public or private cloud. Only prereq for NCache is .NET or .NET Core, depend upon operating system that you choose. We also are coming up with our SaaS model within Azure and AWS. So, it's fully supported. Our marketplace Image is also published. So, there are many ways you can use NCache in Azure.

NCache is a TCP/IP based cache clustering protocol. So, it requires an IP address and a port. So, I’ll just keep everything default on the screen.

If my cache becomes full, I have two options, I can reject new updates and keep using cache for reading or I can turn on evictions and it would automatically remove some data to make room for the newer items.

For sessions, for view state, for response caching and even for object caching, if data is of important nature, I highly recommend that you turn off eviction and provide a big enough gas size so that it never becomes full and even if it becomes full you can change the cache size at runtime. But, if it's okay to afford it's okay to actually lose some data, if you can afford that, so, in that case you can enable evictions and at cache becoming full it would automatically remove some data and make room for the newer data. So, that's what eviction does.

Start this cache on finish and auto start this cache on service startup. So, each time my server gets rebooted, cache would automatically be started up and that's it. That's how easy it is to configure a two node cache cluster with NCache.

Simulate Stress and Monitor Cache Statistics

So, our cache is up and running. It's activated as well, so, I’m going to quickly go ahead and test it. Statistics window. So, these show me perfmon counters both servers and then monitoring aspects of NCache.

Very good. So, it gives me server-side and client-side dashboard report view as well. So, all the parameters are there.

Next thing is to test this cache. No application is connected, so, I’m going to go ahead and do that. That’s the name of the cache and behind the scenes this is using client-side configurations to connect and would connect to my cache cluster and start simulating requests. As you can see Requests per Second counter and Average Microsecond per cache operation counter showing activity. We have Additions, Fetches, Updates happening as well and we have Cache size counter showing up, CPU, Memory and we have Client IPs which are currently connected to the cache.

Let me go ahead and run another instance of this tool.

Question: Another question, while you were doing this is, can we monitor all the clients connected to our caches?

Absolutely. That's one of very powerful features within NCache. I can show this with the help of this example. Now that we have two clients connected. First of all, I can see the client dashboards. So, these two clients are being run from 107 and I can see Read Operations being made from this client box. If I’m running applications from my box which I would do, you would see my box statistics here as well and similarly we can also see the processes, their process IDs, ports, bytes sent and received from here as well. But, I think main thing that you're looking for is different monitoring aspects from the client-side. So, we have Read Operations per second, Additions per second, Updates per second, Fetches.

So, we have complete set of client-side counters, which are available as part of NCache, right, and Cache health and these are all server-side counters but all of these counters, that you're showing under client-side these can be added and you can review all of those for your client application servers as well.

So, NCache is very extensive in its support for client-side monitoring. I hope that answers your question.

Now, I have run actually I’m able to run two instances of stress test tool and you can see about 800 to 900 Requests per second by each NCache server. So, about 1500 Requests per second on average are being handled.

So, that was a quick demo. I’m going to stop these tools. Next, I’m going to show you the actual features that you can actually utilize. So, we have spent good amount of time explaining how NCache is very scalable.? How it is deployed? How to configure and create a cache cluster?

Now, next thing that I would like to highlight is how to utilize NCache in ASP.NET Core applications and what are the primary use cases. We covered different use cases. So, I'll go get started with web front-end caching use cases within ASP.NET Core.

NCache ASP.NET Core Session Caching

So, first feature that I’m going to highlight is NCache ASP.NET Core session caching. We have two options here. You can use, if you're already using sessions and most chances are that you would be using IDistributedCache Interface. So, you can plug in NCache as a provider for session caching through IDistributedCache. You have standalone in-memory provider. You have a SQL server provider. Standalone is a single point of failure. It's not scalable. SQL server is also a single point of failure in some cases. It's not scalable and it's very slow.

NCache provider is super fast. It's in-memory. It's very scalable. It's not single point of failure and you increase capacity at runtime. So, you can have more and more servers added and you can you know achieve unlimited scalability out of NCache. So, IDistributedCache, let's quickly review that. So, here is our guess game, IDistributedCache.

public void ConfigureServices(IServiceCollection services)
{
	//Add framework services
	services.AddMvc();

	services.AddNCacheDistributedCache(configuration =>
        {
	   configuration.CacheName = "MyNCacheCache";
	   configuration.EnableLogs = true;
	   configuration.ExceptionsEnabled = true;
        });
}

public void Configure(IApplicationBuilder app)
{
	app.UseNCacheSession();
}

All you need to do is, services dot add NCache Distributed Cache and then say app Use NCache Session and here is a sample application that is going to do just that.

public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            //To test sessions with NCache Distributed Cache, uncomment the following lines and comment the line after it
            services.AddNCacheDistributedCache(Configuration.GetSection("NCacheSettings"));
			services.AddSession();			
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

Second option here is NCache session provider which has advanced features and I have a sample application for that as well. So, let's actually focus on this. Primarily, because our provider is much more feature rich extensive in comparison to IDistributedCache, so, I would like to highlight this and as part of that you would see all the features that IDistributedCache provider also gives you.

So, coming back here. First of all, you need NCache NuGet package, which I've already added inside this. So, if we have AspNetCore.Sessions.NCache, right. So, that's the NuGet package and let me just quickly come back here and review session here as well and this has NCache.Microsoft.Extensions.Caching. So, IDistributedCache session NuGet packages is slightly different than the NCache actual session provider.

So, I’m going to focus on this one right here. All you need to do is…. First of all come to Startup.cs. You have services.AddNCacheSession and it takes NCache settings in the configuration.

... 
    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddMvc();
        
        //To test sessions with NCache Distributed Cache, uncomment the following lines and comment the line after it
        //services.AddNCacheDistributedCache(Configuration.GetSection("NCacheSettings"));
		//services.AddSession();
			
        services.AddNCacheSession(Configuration.GetSection("NCacheSettings"));
    }
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
... 

And, inside configuration you specify all NCache provider settings. For example, I’m using ASP.NET Core cache. SessionAppId is an app id attribute that app gets appended with the cache item key. Your session id becomes the cache key and this attribute would get appended to it. So, you can uniquely view sessions of different applications inside NCache and then you set up some session locking. You can, if you set this to true. All right, so, if you set this to true that would allow you to simply lock session, so that concurrent access is not allowed.

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "NCacheSettings": {
    "CacheName": "mycache", //Replace "myPartitionedCache" with the name of your cache
    "SessionAppId": "GuessGame", //(Optional)Specifies an identifier to make sure that session ID remains unique in case multiple applications are using the same cache.
    "EnableSessionLocking": false, //(Optional)If this flag is set, NCache Session Store Provider exclusively locks the session-store item for which multiple concurrent requests are made. The default is false.
    "SessionLockingRetry": -1, //(Optional)If enableSessionLocking is true and this integer is not less than 0, NCache Session Store Provider will return empty session after sessionLockingRetry, which specify the number of retries to acquire a lock. The default is -1.
    "EnableLogs": false, //(Optional)When this flag is set, store provider logs all error information. The log files are created in %NCHOME%/log-files/SessionStoreProvider. The default is false.
    "EnableDetailLogs": false, //(Optional)When this flag is set, store provider logs all debugging information. The log files are created in %NCHOME%/log-files/SessionStoreProvider. The default is false.
    "ExceptionsEnabled": false, //(Optional)Specifies whether exceptions from cache API are propagated to the page output. Setting this flag is especially helpful during development phase of application since exceptions provide more information about the specific causes of failure. The default is false.
    "OperationRetry": 0, //It specifies the number of times server will retry the operation, in case connection is lost with a server while an operation is executing. Its default is zero.
    "operationRetryInterval": 0 //It specifies the time interval between each operation retry, in case connection is lost with the server. Its default value is zero.
  }
}

So, you're only able to read session. Write access is only granted to the current request which has the log and then we have detailed logs available. Single-site and multi-site sessions are available. So, all these features are only available if you plug in our ASP.NET Core session provider. IDistributeCache sessions are supported but those are limited. So, I’m going to run this real quick and that would simulate an application which would eventually connect to this ASP.NET Core cache.

Actually, let me stop this. Let me add my box as a client first. So, here all I need to do is add the client box which is my box right here.

There you go. I have all the configurations now and now I can run this application one more time and that should be allowed to connect to my cache cluster. Very good. So, it will take some time for the first time instantiation.

So, this would create a session object inside NCache. I will see, if I go to the monitoring aspect I would see one client connected. Once the provider gets initialized that client would make connection to 107 and 108 and would create session objects inside NCache. At the moment only one session object would be created because that's the main user. I'm going to be the one user that's logged in but if you have multiple application users you would see multiple sessions created inside NCache. But, idea here is, it's very simple that all you need to do is add these two lines of codes. Add NCache session and then say Use NCache session.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();

//To test sessions with NCache Distributed Cache, uncomment the following lines and comment the line after it
//services.AddNCacheDistributedCache(Configuration.GetSection("NCacheSettings"));
//services.AddSession();
			
services.AddNCacheSession(Configuration.GetSection("NCacheSettings"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();
    //To test NCache Distributed Cache, uncomment this and comment the line below this
    //app.UseSession();
    app.UseNCacheSession();
... 

These extension methods are going to cover everything. First instantiation takes slightly longer time, that's what we've seen in ASP.NET Core but once it's, the applications are up and running we should be able to test it real quick. There you go. One client is already connected, so, I think we're there.

In the meantime, let me run a quick command which says export. This tool dumps all the cache keys which are currently there.

So, I'm expecting one session object added in the cache because one client is connected and that should allow me to there are two session. One from the previous attempt and one from this one, is already there. There you go.

This is a guessing game which allows you to guess a number and it adds those numbers inside session and it also displays those number. So, let me just guess a bigger number and that's how it's going to make request to NCache.

So, it's pretty simple application but if you come back here, we have two items in the cache which are there and you must have seen those two requests that I’ve just made onto NCache server side.

So, that completes our first demonstration, that you can plugin NCache for session caching. There are a lot of features. Single site, multi-site sessions are supported. Session locking is supported. Extensive logging is available. It's very secure. We have security and encryption on top of it. So, it's a full package, if you're planning to use NCache for session caching.

ASP.NET Core Response Caching

Second feature here is ASP.NET Core response caching. In this, we have http headers on the applications. You can choose to have page outputs cached and you use NCache response caching middleware for caching and it's again through IDistributedCache interface. So, if I come back right here, we have this sample application.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Alachisoft.NCache.Caching.Distributed;

namespace ResponseCaching
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCaching();
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            #region Option1
            // Reading NCacheSettings using appsettings.json

So, all you need to do in order to use this is, use this NuGet package NCache.Microsoft.Extensions.Caching. This will cover sessions, IDistributedCache for object caching and response caching in one NuGet package.

And, then next thing that you need to do is services.AddNCacheDistributedCache.

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCaching();
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            #region Option1
            // Reading NCacheSettings using appsettings.json
            services.AddNCacheDistributedCache(Configuration.GetSection("NCacheSettings"));
            #endregion

            #region Option2
            // Reading NCacheSettings using hardcoded values
            //services.AddNCacheDistributedCache(options =>
            //{
            //    options.CacheName = "myPartitionedCache";
            //    options.EnableLogs = true;
            //    options.ExceptionsEnabled = true;
            //});
            #endregion

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

So, that's an IDistributedCache interface that gets plugged in but we'll be using it for response caching and in app settings we have the cache names and some logging settings that we have set up and if I come back right here you would have response caching configured as well, services.AddResponseCaching();

...
namespace ResponseCaching
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCaching();
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
...

So this application expecting response caching to be done. It would be done through NCache using IDistributedCache interface. So, I'm going to run this real quick because it takes some time and then expect it to be able to actually connect to NCache and then we will actually show you the data being added.

If I show you the code. You know, you can also specify configurations inline. For example, ‘AddNCacheDistributedCache’ and you can say cache name ‘demoClusteredCache’ or ‘aspCoreCache’ and you can specify all the settings inline

public void ConfigureServices(IServiceCollection services)
{
...
   services.AddNCacheDistributedCache(configuration => 
   {
	configuration.CacheName = "demoClusteredCache";
	configuration.EnableLogs = true;
        configuration.ExceptionsEnabled = true;
   });
...
}

or you can just use the NCache setting and give settings through NCache settings, right here. It could be through configuration or through inline settings. So, it's very simple on that front.

public void ConfigureServices(IServiceCollection services)
{
   services.AddResponseCaching();

   //remaining services here
   ...
   //Add NCache services to the container
   services.SetNCacheSessionConfiguration ( 
	Configuration.GetSection("NCacheSettings"));

   services.AddNCacheDistributedCache();

   services.AddMvc();
}

If I come back right here now we are expecting two clients to be connected because the response caching application would also make a connection to NCache and we would see some items in the cache as a result of that.

Question: How exactly is the data encrypted and kept secure?

Let me show this response caching sample and then I’ll show you the security encryption features inside NCache. So, we have the application running and as you can see, we have some items added. If I simply refresh this we have the values being updated from here and this is being, this page output content is stored inside NCache through our response caching and if I show you the cache keys, we now have some more items in the cache. So, two items in addition to session items which are already there.

Now, coming back to security encryption. Those are very extensive inside NCache. We have security and encryption features. There are many security providers that you can choose from. We have AES and DES providers. Multiple of those. We have FIPS compliant encryption providers and we have TLS 1.2 supported as well. So, transport level security is also available with NCache. So, that's what covers your encryption. Where you can have end-to-end encryption between your client and server boxes and then from a security standpoint, you can choose who could be cache admins, who could be cache users and we have active directory based LDAP based encryption security provider that you can plug in.

So, if there are any specific questions, you can send us an email and we'll work with you and share all those details on how to configure these. But we have very extensive support inside NCache. So, coming back here. So, this covers our ASP.NET Core Response Caching.

ASP.NET Core SignalR Backplane

Next feature is ASP.NET Core SignalR Backplane. In this, if you're using a web farm you can use NCache as a backplane for it and in comparison to conventional backplanes, NCache is a lot faster, very reliable and very scalable as well.

So, here is the NuGet package that I’ve added. AspNetCore.SignalR.NCache that you can search. It's already installed. All you need to do, again it's taking a cache name through app settings and then here is the extension method. So, if you have services.AddSignalR, on that we have an extension method where it adds NCache has a SignalR Backplane for ASP.NET Core applications.

// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.Configure<NCacheConfiguration>(Configuration.GetSection("NCacheConfiguration"));
    services.AddSignalR().AddNCache(ncacheOptions =>
    {
        ncacheOptions.CacheName = Configuration["NCacheConfiguration:CacheName"];
        ncacheOptions.ApplicationID = Configuration["NCacheConfiguration:ApplicationID"];
        // Uncomment in case of cache security enabled.
        //ncacheOptions.UserID = Configuration["NCacheConfiguration:UserID"];
        //ncacheOptions.Password = Configuration["NCacheConfiguration:Password"];
    });
}

I’ll run it real quick because it's taking some time. I think a lot of things are running. So, I’ll run this sample. So, that would spin up a chat application and it would be using, behind the scenes it's using Pub/Sub Messaging of NCache. So, for that what I will really do is, I will come back right here and on the server side I would show you some performance counters, which would allow you to specifically manage and monitor the Pub/Sub Messages and SignalR is using Pub/Sub Messaging behind the scenes. So, let me just say SignalR, right. So and if you notice here we have messaging section right here. Where we can see message store size, message count, delivered per second, expired per second, published, topics count.

Topics allows you to have separation of concern. Multiple applications using NCache as a SignalR Backplane they would have multiple topics created inside NCache. So, topics of a, message of a similar nature can be given to a separate topic. For example, you can create a topic for orders. You can create a topic for messages customers. So, you can have different messages in different topics and subscribers connected to a separate topic are only given those messages related to them. So, already see some activity here which means my application is up and running. So, if I come back to server dashboard, now we have three applications running. I won't see anything in the regular statistics. I would see everything in the messaging statistics. So, you can see message count.

So, there were some messages, store size, it’s about 800 bytes. Message published and then we have I think other parameters on the date not there because we don't have any expiry as yet.

So, coming back to my application, I would run another instance of this. All right, so, I have two applications. Should have run it in incognito but test message as soon as send this, it's sent here as well. Test message 2 and you would see that message on the other side as well, right, and if I come back right here, we have some activity going on here.

So, it uses Pub/Sub Messaging behind the scenes and it allows you to create a backplane. So, SignalR itself, the use case for it, that it emphasizes on the push notification instead of user polling which you can push content to your users. But with web farm, there's a limitation that you can only push content to the connected clients to that web server. If you have multiple web servers you need a central body and Backplane by ASP.NET and ASP.NET Core resolves that.

So, you can use NCache as a Backplane for that. It's a communication platform where all the messages are relayed to NCache, sent to NCache and then NCache in turn sends to all the web servers, broadcast it and those web servers are able to transmit those messages to their end users, eventually. So, that's what makes it very fast the overall architecture very fast because the NCache is in-memory. It's super-fast, very scalable, very reliable, highly available.

Okay. So, I think we're good on this front. Any questions? Otherwise I’m going to move on and talk about the data caching aspect. So, we'll spend last 10-15 minutes on data caching, some of the features. There are a lot of features on this front. So, I would quickly showcase some.

So, first of all there are many ways you can use NCache for database caching or the app data caching. Where you cache data inside NCache & you save trips to the backend database. If you're already using IDistributedCache, although that's very limited, you can plug in NCache as a provider.

I have a sample application right here. Again, it's very simple. You need the NuGet package added. It's ASP.NET Core and NCache.Microsoft.Extensions.Caching.

public async Task<IActionResult> onPostResetCachedTime()
{
    var currentTimeUTC = DateTime.UtcNow.ToString();
    byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
    var options = new DistributedCacheEntryOptions()
        .SetAbsoluteExpiration(TimeSpan.FromSeconds(20));
    _cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);

    return RedirectToPage();
}

So, that covers IDistributedCache and then you need app settings where you specify the name of the cache and inside startup.cs you plug in. services.AddNCacheDistributedCache give the settings through config or you can use option two where you specify configs, all the settings through code directly.

public void ConfigureServices(IServiceCollection services)
    {
        // Reading NCacheSettings using appsettings.json
        services.AddNCacheDistributedCache(_config.GetSection("NCacheSettings"));

        services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2);
    }

And, in the index.html.cs we have some methods here. Where we're calling cache.GetAsync.

public string CachedTimeUTC { get; set; }
public async Task OnGetAsync()
{
    CachedTimeUTC = "Cached Time Expired";
    var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

    if(encodedCachedTimeUTC != null)
    {
        CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
    }
}

And one thing that you need to note is this IDistributedCache interface, for example, if I show you the GetAsync, it's taking a byte array back. So, it's not a serialized or deserialized object that you're getting. You have to do the serialization and deserialization yourself.

using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Caching.Distributed
{
public interface IDistributedCache
{
    byte[] Get(string key);
    Task<byte[]> GetAsync(string key, CancellationToken token = default);
    void Refresh(string key);
    Task RefreshAsync(string key, CancellationToken token = default);
    void Remove(string key);
    Task RemoveAsync(string key, CancellationToken token = default);
    void Set(string key, byte[] value, DistributedCacheEntryOptions options);
    Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, 
    CancellationToken token = default);
}
}

What we've done in this sample, we actually taken this in such a way that we're serializing object before adding and after retrieving we're deserializing it, right. So, we're getting the string back through this.

public string CachedTimeUTC { get; set; }
public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if(encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }
    }

So, that is something that we've done extensively and that's what you have to do. With NCache directly used as your app data caching platform you don't have to do all of this. NCache would serialize and deserialize your objects automatically. You just need to mark them as serializeable.

So, I’m going to run this sample real quick and that should allow me to showcase the IDistributedCache interface as well and finally while this is being run I would also like to build, so, this is our IDistributedCache interface and this is the second option here, which I’m showing right here, is the direct NCache APIs. Here are our APIs.

/*Cache Connection*/
Cache cache = NCache.InitializeCache("myCache");
cache.Dispose();

/*Fetching Data*/
Employee employee = cache.Get<Employee>("Employee:1000");
bool isPresent = cache.Contains("Employee:1000");

/*Writing Data*/
cache.Add("Employee:1000", employee);
cache.AddAsync("Employee:1000", employee);

cache.Insert("Employee:1000", employee);
cache.InsertAsync("Employee:1000", employee);

Employee employee = (Employee) cache.Remove("Employee:1000");
cache.RemoveAsync("Employee:1000");

Alternatively, which we highly recommend that you get more control if you manage everything through direct NCache API calls. You connect to NCache using initialize cache or get cache in the current versions and then you say cache.Get to retrieve items cache.Add to add items. Add Async is also available Insert and Insert Async is also available and then you can call cache.Remove. So, that's the second approach which I will show once this sample runs and IDistributedCache is supported but our direct APIs are more extensive feature rich in comparison.

Question: Can we use NCache API along with IDistributedCache and for that where can we get the samples?

So, first of all yes, you can use IDistributedCache. You must already be using IDistributedCache. So, you can plug in NCache as a source for that. So, that could be a starting point and then in addition to that you can also start using our direct APIs in the same application. So, yes you can do that. We don't limit you to use one or other. All these use cases that we've discussed Session Caching, Response Caching, SignalR Backplane, IDistributedCache or direct NCache API calls. Five of these features, you can use all of them in single application as well. So, that's the beauty of NCache where you can combine all those use cases.

Sample applications, all of these samples are, they come installed with NCache, right. So, we're not using anything custom here. So, all of these are available in program file sample, NCache samples. These are published on our website as well.

So, this sample has run. So, if I reset cache time, it would do that and if I quickly come back right here, I would showcase the cache keys and now you would see some more cache keys. I think some items have been expired but you see cache time UTC. Some response caching objects must have been expired.

So, that's how you use the IDistributedCache interface with NCache.

Now I will showcase the direct NCache API calls, which we highly recommend. That is something that you can also do. So, for this you can use the Alachisoft.NCache.SDK NuGet package. That is our complete SDK, which brings in the entire client side resources and all the features can be exposed on the client end using this SDK and based on this I can say include these name spaces Alachisoft.NCache.Client and you can also include Alachisoft.NCache.Runtime because there are some advanced features that you can use through that.

using Alachisoft.NCache.Runtime;
using Alachisoft.NCache.Sample.Data;
using Alachisoft.NCache.Client;
using System;
using System.Configuration;
using Alachisoft.NCache.Runtime.Caching;

namespace Alachisoft.NCache.Samples
{
    public class BasicOperations
    {
        private static ICache _cache;
    }
}
...

ICache interface. This is our own interface unlike IDistributedCache, right. So, we have a lot of features as you can see in comparison and within this these we have multiple features as well. So, these are some objects on ICache.

So, you can initialize the cache. Initialization is done through NCache.Client. CacheManager.GetCache, so, that's how you get connected to it. You get a cache handle and using that cache handle you can set up some time based expiry. Give the object which is customer in this case, AddObjectToCache and we're getting a customer. Then you say cache.Add a key value item is added. So, customer object would automatically be serialized while you would add into the cache and then retrieval is through cache.Get, update through cache.Insert, where you can change some values. Change expiry time and then you can call cache.Remove to remove data as well.

So, within this sample if quickly. All right. So, it's getting the cache from the app settings. If I give the name of this cache to be asp core cache, this will start using the same cache for data caching as well and you've already seen this where I have used same cache within different applications but, if it's one big application using all these features in conjunction with one another, in combination to one another, that's also supported.

So, while this is being run, there are many features. I'm just showing one or two in the samples because those are just basic operations and this would add some data in the cache. It may not stop at any point in time, I’m not too sure but let's see. In the meantime, if I show you one client is connected. It has made some operations, right. Let's hope it. Let me put a break point somewhere and run it one more time.

So, what it really did was, it actually initialized the cache added an object, retrieved it, updated it, retrieved it again, then removed it and then cache got disposed. So, before dispose I’ve put a break point, so hopefully, you can now see that initialized, added, fetch, details updated, updated in the cache. So, all these methods have been executed.

So, that's how simple it is to run this sample. All you need is the NuGet package and the namespaces to be included and then play around with different APIs, which I’m going to show here.

Many features are available. I'm going to spend last 5 - 6 minutes on different features, which are available as part of our direct NCache APIs. So, you can use time based expiry. Absolute and sliding expiration is available. Absolute is when you give a time upfront and data would be removed after that time elapses. Sliding is, if you keep using that data, let's say you have a 10 minutes of sliding expiry, if you don't access that item for 10 minutes, only then it's removed. Otherwise, it would stay in the cache, if you keep using that object.

You can synchronize your cache with database. Any change in the cached item content, right. For example the cached item any change in the database record. If you have cached items which belong from the database, any change in the database would invalidate the item from the cache and we have a SQL dependency for that, which is event based. We have DB based dependency, DB polling dependency, which is polling based. It pools for changes and if it finds some records in the database have been changed, it would remove the corresponding items from the cache. .NET CLR stored procedures, where you can make direct NCache API calls from the database servers directly. You can synchronize your cache with non-relational databases as well. File dependencies available. You can make cache items dependent on files. Or it could be also custom dependency, where you run a code register with NCache and that would determine whether that item has to be expired from the cache. So, based on your business logic you can expire items from the cache. We have key base dependency. You can use that to handle relationships between cached items. So, one-to-one, one-to-many and many-to-many relationships can be managed inside NCache.

Server side support, code support is very extensive. You can use cache aside pattern or you can use Read-through and Write-through pattern, where you read into the cache, from the cache, and if data is not in the cache, you can read through the cache by running your provider. It's an interface that you implement and register with us and you can read-through the cache and get that item or wreck it from the backend database.

And write-through is opposite of it, where you update the cache and then you can also automatically update the backend database by calling your write-through handler, an interface that you implement and register and write-behind would update the backend database asynchronously, which is very fast in comparison, where you don't have to even for write operations your cache item is going to be only updated in the cache and your application returns and behind the scenes then cache would update the back-end databases.

Groups, subgroups are available. You can create logical collections inside NCache. Tags are supported. You can attach keywords on the items and you can retrieve, remove or fetch or even play around or search items based on their corresponding tag.

SQL Like searches are available. SQL and LINQ searches are supported. You can search items based on their attributes. You can run a search criteria, which can go something like select products where product is the object type where and say select products where product.price is greater than 10 and product .price is less than 100. So, based on your specified criteria NCache would formulate the result set in your applications.

And, LINQ queries are also supported. These are parallel. Data indexing is supported as part of this. You need that as a must that makes it very fast searching out of NCache.

Pub/Sub Messaging and Events

Pub/Sub messaging and events is a separate feature. We, have async event driven Pub/Sub messaging platform. We have criteria based continuous query, where you can map a data set and get events out of that data set. For example, you can map all products, where product prices are less than 100. So, that could be your data set. Any change in that data set you would get notified.

Key base events are available. So, you can get notified if an item gets updated or removed or it could be cache level where all items are being monitored.

Dynamic Cache Cluster

Finally some details about NCache clustering. It's based on dynamic cache clustering protocol. NCache allows you to add or remove servers on the fly. So, there's no single point of failure. In case any server goes down, it's managed within the protocol. If you add more servers, it automatically starts using newly added server. If a server goes down you have no down time in even in that case and there's no single point of failure primarily because there are multiple servers hosting this cache.

Adding and removing servers is seamless, as far as, your applications are concerned. Gives you 100% up time, a highly available cache scenario and then we have already covered topologies.

We have some Client Cache. WAN Replication features. You can have site to site replication between active-active or active-passive sites. That’s also managed.

WAN Replication of Distributed Cache

So, you know the list goes on. I think we've covered a lot of details and I wanted to give you a quick overview of how you can actually utilize NCache in ASP.NET Core. So, we're already heading towards the end of this presentation. I think one hour marker.

I think, let's conclude this at this point. Please let me know if there are any questions so far? Otherwise I’m going to hand it over to Zack and then he can take it from there. Any questions? We’ll leave the floor open for about a minute. If you have any questions or you're still thinking up of some, feel free to toss them out.

And while we're at it, we've pretty much covered all these features that you're seeing. ASP.NET Core Sessions, Response Caching, SignalR Backplane, IDistributedCache, Data Caching and direct NCache API calls and we even have sample applications for Pub/Sub Messaging, if you're interested separately in this section, you can get a separate sample application available as well.

Question: Will this presentation be available later?

Yes it will be. We will be able to not only get a recording of the webinar but also we'll have the slides available if you'd like to download and use those as well and you will get an email linking you to the recorded webinar once it’s ready. So, you can always come to our website once it’s live and under recorded webinars you can you can find them.

Question: What is the difference between IDistributedCache and NCache ASP.NET Core Session Store Provider?

Okay, so, I believe the question is more focused on difference of session usage through IDistributedCache and Session use it through our cache provider. So, on a high level IDistributedCache is limited in terms of its functionality. NCache provider gives you more control. First of all, you can use session locking. IDistributedCache Session is non-locking based store. You can use single-site and multi-site sessions. That's a specific feature inside NCache which is not available in IDistributedCache. Although, NCache supports IDistributedCache Session Provider as is. It gives you a lot of benefits on top of IDistributedCache but when we compare IDistributedCache Sessions with NCache and you know, NCache Session store provider with NCache, that's where NCache provider has a clear edge. Then there are a lot of features such as session sharing between different applications. There is a session App ID attribute that you can specify. Your exceptions can be logged into event log, cache logs can be created, client logs can be created. So, there's a huge list of features which are different between these two.

So, it's highly recommended that if you're using session caching with NCache, so I would personally recommend to review our session store provider in comparison to IDistributedCache Session with NCache because you get a lot of benefits.

Question: How can grouping data make that data retrieval easier?

Right. So, you attach a group to multiple items and, then there are group based APIs where you say get group data and you get all those items at once. So, you don't have to give keys of those individual objects separately. So, it makes it with one API call, all the group data can be retrieved. You can remove data in one API call. So, that's how retrieval and update process is easier in comparison.

 

Signup for monthly email newsletter to get latest updates.

© Copyright Alachisoft 2002 - . All rights reserved. NCache is a registered trademark of Diyatech Corp.