CacheDependency and the VirtualPathProvider for paths wirh dependencies - asp.net-mvc

We are using the BundleTransformer library in an ASP.NET MVC 4 setup. Our web application is a rather thin layer, with all server logic handled in a backend service.
After an installation all resource will be installed along side the web application on the file system, but for update reasons we need to be able to serve resources like JavaScript and CSS (LESS) from the service - they will then override the local (file system) versions.
In essence, if available from the service, we serve a requested resource from there. If not, we fall-back to the file system and serve the file from there.
It was all working like a charm, then we introduced LESS and #import statements, now things are not working so fine anymore.
We still want to cache the result of the LESS transformation in the Http Cache, and we would like to invalidate that result whenever a dependency changes. The current implementation in the VirtualPathProvider does that, but if I update a single file (a JavaScript file for instance), it will not be updated.
My VirtualPathProvider looks like this:
public class ViewsAndScriptsPathProvider : VirtualPathProvider {
private static IApplicationServiceResourcesManager ResourceManager {
get { return InstanceProvider.Get<IApplicationServiceResourcesManager>(); }
}
public override bool FileExists(string virtualPath) {
var exists = ResourceManager.Exists(virtualPath);
return exists || base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath) {
VirtualFile file;
if (ResourceManager.TryGet(virtualPath, out file)) {
return file;
}
return base.GetFile(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) {
bool isRelevant = ResourceManager.IsRelevant(virtualPath);
if (isRelevant) {
var cachekeys = virtualPathDependencies.Cast<string>().Where(dependency => virtualPath != dependency).ToArray();
return new CacheDependency(null, cachekeys);
}
if (IsBundle(virtualPath)) {
return new CacheDependency(null, new[] { ResourceManager.ComponentCacheKey }, utcStart);
}
return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
private bool IsBundle(string virtualPath) {
return virtualPath.StartsWith("~/bundles/", StringComparison.InvariantCultureIgnoreCase)
|| virtualPath.StartsWith("~/css/", StringComparison.InvariantCultureIgnoreCase);
}
public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) {
byte[] bhash;
string filehash;
if (ResourceManager.TryGetFileHash(virtualPath, out bhash)) {
filehash = BitConverter.ToString(bhash);
} else {
filehash = Previous.GetFileHash(virtualPath, virtualPathDependencies);
}
return filehash;
}
}
Think of the ResourceManager as a proxy / cache towards the service.
My big issue is that I don't understand exactly the CacheDependency work. If I add a cachekey (the second parameter) that includes the virtualPath itself, then I get an infinite loop in the server.
If I simply return null it won't work for LESS #imports.
If anybody could explain or point to how the VirtualPathProvider is supposed to implement the GetCacheDependency and the GetFileHash functions, I might be able to solve this one.

I actually worked out a solution some time ago, and it has to do with how the HttpCache works with the CacheDependency object. It is rather complex however.
Basically I have three scenarios:
The resource is hosted only on the file system.
The resource is hosted only at the service.
The resource is hosted at both the service and the local file system.
For 1. I use the CacheDependency object for the file location. This is standard and how the VirtualPathProvider works by default.
For 2. I use a custom (derived) ResourceCacheDependency which implement logic to invalidate itself when the proxy has a new version.
For 3. I use the AggregateCacheDependency object that has both a CacheDependency to the physical file and a ResourceCacheDependency object.
For all virtual path dependencies (the list of dependencies for the resource) I repeat the above assumptions, and build it into the AggreateCacheDependency (it will potentially have a lot of dependencies).
In my custom implementation of the VirtualPathProvider I override the GetCacheDependency method to return the relevant CacheDependency object based on the above analysis.

Related

UWP Template 10 and Service Dendency Injection (MVVM) not WPF

I have spent over two weeks searching google, bing, stack overflow, and msdn docs trying to figure out how to do a proper dependency injection for a mobile app that I am developing. To be clear, I do DI every day in web apps. I do not need a crash course on what, who, and why DI is important. I know it is, and am always embracing it.
What I need to understand is how this works in a mobile app world, and in particular a UWP Template 10 Mobile app.
From my past, in a .net/Asp app I can "RegisterType(new XYZ).Singleton() blah" {please forgive syntax; just an example} in App_Start.ConfigureServices. This works almost identical in .netcore, granted some syntactic changes.
My problem is now I am trying to provide my api is going to an UWP app that needs to digest my IXYZ service. By no means do I think that they should "new" up an instance every time. There has to be a way to inject this into a container on the UWP side; and I feel I am missing something very simple in the process.
Here is the code I have:
App.xaml.cs
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// TODO: add your long-running task here
//if (args.Kind == ActivationKind.LockScreen)
//{
//}
RegisterServices();
await NavigationService.NavigateAsync(typeof(Views.SearchCompanyPage));
}
public static IServiceProvider Container { get; private set; }
private static void RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<IXYZ, XYZ>();
Container = services.BuildServiceProvider();
}
MainPage.xaml.cs:
public MainPage()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
MainPageViewModel:
public class MainPageViewModel : ViewModelBase
{
private readonly IXYZ _xyz;
public MainPageViewModel(IXYZ xyz)
{
//Stuff
_xyz= xyz;
}
}
I now get the error:
XAML MainPage...ViewModel type cannot be constructed. In order to be constructed in XAML, a type cannot be abstract, interface nested generic or a struct, and must have a public default constructor.
I am willing to use any brand of IoC Container, but what I need is an example of how to properly use DI for services in a UWP app. 99.9% of questions about DI is about Views (i.e. Prism?) not just a simple DI for a service (i.e. DataRepo; aka API/DataService).
Again, I feel I am missing something obvious and need a nudge in the right direction. Can somebody show me an example project, basic code, or a base flogging on how I should not be a programmer...please don't do that (I don't know if my ego could take it).
You can try to Microsoft.Hosting.Extensions just like ASP.NET, there's an implementation on Xamarin.Forms by James Montemagno, as well it can be used in UWP I have tried and it works perfectly. You have to change some parts in order to get it working.
In OnLaunched Method add Startup.Init();
public static class Startup
{
public static IServiceProvider ServiceProvider { get; set; }
public static void Init()
{
StorageFolder LocalFolder = ApplicationData.Current.LocalFolder;
var configFile = ExtractResource("Sales.Client.appsettings.json", LocalFolder.Path);
var host = new HostBuilder()
.ConfigureHostConfiguration(c =>
{
// Tell the host configuration where to file the file (this is required for Xamarin apps)
c.AddCommandLine(new string[] { $"ContentRoot={LocalFolder.Path}" });
//read in the configuration file!
c.AddJsonFile(configFile);
})
.ConfigureServices((c, x) =>
{
// Configure our local services and access the host configuration
ConfigureServices(c, x);
}).
ConfigureLogging(l => l.AddConsole(o =>
{
//setup a console logger and disable colors since they don't have any colors in VS
o.DisableColors = true;
}))
.Build();
//Save our service provider so we can use it later.
ServiceProvider = host.Services;
}
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
{
//ViewModels
services.AddTransient<HomeViewModel>();
services.AddTransient<MainPageViewModel>();
}
static string ExtractResource(string filename, string location)
{
var a = Assembly.GetExecutingAssembly();
using (var resFilestream = a.GetManifestResourceStream(filename))
{
if (resFilestream != null)
{
var full = Path.Combine(location, filename);
using (var stream = File.Create(full))
{
resFilestream.CopyTo(stream);
}
}
}
return Path.Combine(location, filename);
}
}
Injecting a ViewModel is possible as well which is pretty nice.
With help from #mvermef and the SO question Dependency Injection using Template 10 I found a solutions. This turned out to be a rabbit hole where at every turn I ran into an issue.
The first problem was just getting Dependency Injection to work. Once I was able to get that figured out from the sources above I was able to start injecting my services into ViewModels and setting them to the DataContext in the code behind.
Then I ran into an injection issue problem with injecting my IXYZ services into the ViewModels of UserControls.
Pages and their ViewModels worked great but I had issues with the DataContext of the UserControl not being injected with UserControl's ViewModel. They were instead getting injected by the Page's ViewModel that held it.
The final solution turned out to be making sure that the UserControl had the DataContext being set in XAML not the code behind, as we did with the Pages, and then creating a DependencyProperty in the code behind.
To show the basic solution read below.
To make it work I started with:
APP.XAML.CS
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// long-running startup tasks go here
RegisterServices();
await Task.CompletedTask;
}
private static void RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<IRepository, Repository>();
services.AddSingleton<IBinderService, BinderServices>();
**//ViewModels**
**////User Controls**
services.AddSingleton<AddressesControlViewModel, AddressesControlViewModel>();
services.AddSingleton<CompanyControlViewModel, CompanyControlViewModel>();
**//ViewModels**
**////Pages**
services.AddSingleton<CallListPageViewModel, CallListPageViewModel>();
services.AddSingleton<CallListResultPageViewModel, CallListResultPageViewModel>();
etc....
Container = services.BuildServiceProvider();
}
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
**//INJECT THE VIEWMODEL FOR EACH PAGE**
**//ONLY THE PAGE NOT USERCONTROL**
if (page is CallListPage)
{
return Container.GetService<CallListPageViewModel>();
}
if (page is CallListResultPage)
{
return Container.GetService<CallListResultPageViewModel>();
}
etc...
return base.ResolveForPage(page, navigationService);
}
In the code behind for the Page
CALLLISTPAGE.XAML.CS
public CallListPage()
{
InitializeComponent();
}
CallListPageViewModel _viewModel;
public CallListPageViewModel ViewModel
{
get { return _viewModel ?? (_viewModel = (CallListPageViewModel)DataContext); }
}
In your XAML add your UserControl
CALLLISTPAGE.XAML
<binder:CompanyControl Company="{x:Bind ViewModel.SelectedCompany, Mode=TwoWay}"/>
In your UserControl make sure to add the DataContext to the XAML NOT the code behind like we did with the pages.
COMPANYCONTROL.XAML
<UserControl.DataContext>
<viewModels:CompanyControlViewModel x:Name="ViewModel" />
</UserControl.DataContext>
In the UserControl Code Behind add a Dependency Property
COMPANYCONTROL.XAML.CS
public static readonly DependencyProperty CompanyProperty = DependencyProperty.Register(
"Company", typeof(Company), typeof(CompanyControl), new PropertyMetadata(default(Company), SetCompany));
public CompanyControl()
{
InitializeComponent();
}
public Company Company
{
get => (Company) GetValue(CompanyProperty);
set => SetValue(CompanyProperty, value);
}
private static void SetCompany(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as CompanyControl;
var viewModel = control?.ViewModel;
if (viewModel != null)
viewModel.Company = (Company) e.NewValue;
}
In the end I am not sure if this is an elegant solution but it works.

Trying to implement IVirtualImageProvider in ImageResizer

I'm trying to implement an IVirtualImageProvider plugin for ImageResizer as explained here. I didn't find the instructions hard to follow, however it doesn't seem like any of my images are passing through the plugin. My images are stored in a Windows folder located outside of the ASP .NET root.
Any image path that starts with "assets" or "images" should be handled by the plugin. Here is my implementation of the IVirtualImageProvider and IVirtualFile interfaces:
namespace ImageResizer.Plugins.Basic
{
public class ResizerVirtFolder : IPlugin, IVirtualImageProvider
{
public IPlugin Install(Configuration.Config c)
{
c.Plugins.add_plugin(this);
return this;
}
public bool Uninstall(Configuration.Config c)
{
c.Plugins.remove_plugin(this);
return true;
}
public bool FileExists(string virtualPath, System.Collections.Specialized.NameValueCollection queryString)
{
return (virtualPath.StartsWith("assets", StringComparison.OrdinalIgnoreCase) || virtualPath.StartsWith("images", StringComparison.OrdinalIgnoreCase));
}
public IVirtualFile GetFile(string virtualPath, System.Collections.Specialized.NameValueCollection queryString)
{
return new ResizerVirtualFile(virtualPath);
}
}
public class ResizerVirtualFile : IVirtualFile
{
public ResizerVirtualFile(string virtualPath)
{
this._virtualPath = virtualPath;
}
protected string _virtualPath;
public string VirtualPath
{
get { return _virtualPath; }
}
public System.IO.Stream Open()
{
string sitePath = System.Configuration.ConfigurationManager.AppSettings["PageFilesLocation"];
_virtualPath = _virtualPath.Contains("assets/") ? _virtualPath.Substring(_virtualPath.IndexOf("assets/") + 7) : _virtualPath;
string assetPath = Path.Combine(sitePath, _virtualPath.TrimStart('/').Replace("/", #"\"));
System.IO.FileStream oStream = new FileStream(assetPath, FileMode.Open);
return oStream;
}
}
}
Here's a brief snippet of the Web.config modification I made for the plugin:
<resizer>
<plugins>
<add name="MvcRoutingShim" />
<add name="ResizerVirtFolder" />
</plugins>
</resizer>
ImageResizer.Plugins.Basic.ResizerVirtFolder shows up under registered plugins when I go to resizer.debug.ashx, so I believe that means the plugin is loaded. However, when I put a breakpoint on the FileExists or GetFile functions, it isn't triggered.
I thought to use the VirtualFolder plugin, but it doesn't look like it's included in the download any more. I'm using v 3.4.3.
Edit: Added link to the debug output Gist here.
Longer Edit: I should add that the images that are not showing up do not have query strings in their requests and are not being resized in any way. Does that mean that ImageResizer will not look at them at all, and as a result, the Virtual Image Provider's functions will not be executing in this case?
Another Edit: Looking at this page, it seems like the simplest way to get the images to work in ImageResizer might be to add a different prefix rather than /assets or /images, like perhaps /resize. In this case, should I add an ignore route for /resize or not? There is a route handler provided by my CMS which will eventually try to deal with this route if I do not ignore it.
Well, looks like I found my own solution. Here's the problem:
return (virtualPath.StartsWith("assets", StringComparison.OrdinalIgnoreCase)
StartsWith will always return false because the virtualPath will start with my hostname. Switching to a Contains() statement has this working perfectly.
Great plugin, by the way!

Manage multiple ravendb document stores through castle windsor in an MVC app?

I twist myself around a workable solution to use several databases in RavenDB for an ASP.Net MVC app using Castle Windsor for the wiring.
This is the current installer
public class RavenInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>().Instance(CreateDocumentStore()).LifeStyle.Singleton,
Component.For<IDocumentSession>().UsingFactoryMethod(GetDocumentSesssion).LifeStyle.PerWebRequest
);
}
static IDocumentStore CreateDocumentStore()
{
var store = new DocumentStore { ConnectionStringName = "RavenDb_CS9" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Users).Assembly, store);
return store;
}
static IDocumentSession GetDocumentSesssion(IKernel kernel)
{
var store = kernel.Resolve<IDocumentStore>();
return store.OpenSession();
}
}
The above works perfect but only for one Database.
I can't find the proper thinking how to handle another database. The whole chain starts with a domain service asking for an IDocumentSession. Then the flow is as specified in the above installer. But where/how do I ask for a "SessionToDb1" or a "SessionToDb2"?
The important is of course what connection string to use (where the DB property is specified) but also what indexes to create in respective DB / DocumentStore.
Did anyone accomplish this using Windsor? Am I thinking/attacking it wrong here?
Thanks!
Because you have:
Component.For<IDocumentSession>()
.UsingFactoryMethod(GetDocumentSesssion)
.LifeStyle.PerWebRequest
Your GetDocumentSession method is going to be called any time you inject an IDocumentSession. This is good.
When working with multiple databases, you need to pass the database name as a parameter to OpenSession. So, you need some way to resolve which database you would like to connect to based on the current web request.
You need to modify the GetDocumentSession method to implement whatever custom logic you are going to use. For example, you may want to look at a cookie, asp.net session item, current thread principal, or some other criteria. The decision is custom to your application, all that matters is somehow you open the session with the correct database name.
I've run into this problem before with nhibernate.
I found the best solution is to create a SessionManager class which wraps the Creation of the document store and the Session..
So I.E.
public interface ISessionManager
{
void BuildDocumentStore();
IDocumentSession OpenSession();
}
public interface ISiteSessionManager : ISessionManager
{
}
public class SiteSessionManager : ISiteSessionManager
{
IDocumentStore _documentStore;
public SiteSessionManager()
{
BuildDocumentStore();
}
public void BuildDocumentStore()
{
_documentStore = new DocumentStore
{
Url = "http://localhost:88",
DefaultDatabase = "test"
};
_documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(SiteSessionManager).Assembly, _documentStore);
}
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession();
}
}
// And then!.
Container.Register(Component.For<ISiteSessionManager>().Instance(new SiteSessionManager()).LifestyleSingleton());
// And then!.
public class FindUsers
{
readonly ISiteSessionManager _siteSessionManager;
public FindUsers(ISiteSessionManager siteSessionManager)
{
_siteSessionManager = siteSessionManager;
}
public IList<User> GetUsers()
{
using (var session = _siteSessionManager.OpenSession())
{
// do your query
return null;
}
}
}
Rinse and repeat for multiple databases.!

Monotouch/WCF: How to consume the wcf service without svcutil

Becuase monotouch compile to native code, so it has some limitation such as dynamic invoke is not allowed.
But I have a lot class in .net, that I use the ChannelFactory dynamic to invoke the wcf service: new ChannelFactory(myBinding, myEndpoint); Now in monotouch I should use the slsvcutil to generate the wcf proxy class, but the slsvcutil generate a lot of Unnecessary extra code (huge), and Makes consumers difficult to unit test, due to high coupling with the WCF infrastructure through the ClientBase class.
Is there a better solution except the ChannelFactory? I would rather write the code manually, have more control over how services are invoked such as the ChannelFactory.
==========
ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>(binding, endpointAddress);
return factory.CreateChannel();
//==> It throw exception: MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance
ChannelFactory<T> has a virtual method CreateChannel(). If this is not overridden, it uses dynamic code generation, which fails on MonoTouch.
The solution is to override it and provide your own compile-time implementation.
Below is an old service implementation of mine that at least used to work on MonoTouch. I split it up into 2 partial classes - the first one being linked in all builds, the 2nd only in the iOS builds (allowing the dynamic generation mechanism to still work on windows).
I've stripped it down to only contain 1 service call.
TransactionService.cs:
public partial class TransactionService : ClientBase<IConsumerService>, IConsumerService
{
public TransactionService()
{
}
public TransactionService(string endpointConfigurationName) :
base(endpointConfigurationName)
{
}
public TransactionService(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public TransactionService(string endpointConfigurationName, EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress)
{
}
public TransactionService(Binding binding, EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public AccountBalanceResponse GetAccountBalance( AccountBalanceQuery query )
{
return Channel.GetAccountBalance( query );
}
}
TransactionService.iOS.cs:
ConsumerServiceClientChannel which executes the calls via reflection)
public partial class TransactionService
{
protected override IConsumerService CreateChannel()
{
return new ConsumerServiceClientChannel(this);
}
private class ConsumerServiceClientChannel : ChannelBase<IConsumerService>, IConsumerService
{
public ConsumerServiceClientChannel(System.ServiceModel.ClientBase<IConsumerService> client) :
base(client)
{
}
// Sync version
public AccountBalanceResponse GetAccountBalance(AccountBalanceQuery query)
{
object[] _args = new object[1];
_args[0] = query;
return (AccountBalanceResponse)base.Invoke("GetAccountBalance", _args);
}
// Async version
public IAsyncResult BeginGetAccountBalance(AccountBalanceQuery query, AsyncCallback callback, object asyncState )
{
object[] _args = new object[1];
_args[0] = query;
return (IAsyncResult)base.BeginInvoke("GetAccountBalance", _args, callback, asyncState );
}
public AccountBalanceResponse EndGetAccountBalance(IAsyncResult asyncResult)
{
object[] _args = new object[0];
return (AccountBalanceResponse)base.EndInvoke("GetAccountBalance", _args, asyncResult);
}
}
}
EDIT: I just tested this with the latest MT (5.2) - it no longer needs all that extra boiler plate I had in there before, just the CreateChannel() override. I've cleaned up the sample code to match.
EDIT2: I added an async method implementation.
I think you might be confusing terms here - ChannelFactory is a generic type, not a dynamic.
According to MonoTouch documentation, although there's limitations to the Generics support in MonoTouch, ChannelFactory should be okay here.
Have you tried using ChannelFactory?

ASP.NET MVC Disable view caching in overridden VirtualPathProvider

I am doing some dev work using portable areas so I have an overridden VirtualPathProvider.
My public override bool FileExists(string virtualPath) seems to get called only every few minutes, meaning that MVC is caching the views.
This is probably great in production but I can't figure out how to turn it off in dev. I want the VirtualPathProvider to get called on each and every use of the view.
Any suggestions?
Answering my own question for the sake of future generations....
We ended up overriding the GetCacheDependency call to ensure that the view is never cached. (We cache views manually). We had to create a FakeCacheDependency that lets us use the last modified date from our cache.
In our application, our virtual views are called CondorVirtualFiles. (When building a views engine, you need to give it a cool name.)
public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
{
var view = this.GetFile(virtualPath);
if (view is CondorVirtualFile)
{
FakeCacheDependency fcd = new FakeCacheDependency((view as CondorVirtualFile).LastModified);
return fcd;
}
return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
public class FakeCacheDependency : System.Web.Caching.CacheDependency
{
public FakeCacheDependency(DateTime lastModified)
{
base.SetUtcLastModified(lastModified);
}
public FakeCacheDependency()
{
base.SetUtcLastModified(DateTime.UtcNow);
}
}

Resources