MvcSiteMapProvider + Autofac + ISiteMapNodeVisibilityProvider from another assembly - asp.net-mvc

I'm having the toughest time figuring out how to register a custom ISiteMapNodeVisibilityProvider (SiteMapNodeVisibilityProviderBase) using Autofac in MvcSiteMapProvider.
Everything was working fine up until the point that I moved the visibility provider to another assembly. Now, no matter what I try, I always get
The visibility provider instance named 'MyWasWorkingVisibilityProvider, MyNewAssembly' was not found. Check your DI configuration to ensure a visibility provider instance with this name exists and is configured correctly.
According to the MvcSiteMapProvider documentation and code, it appears I need to somehow into the SiteMapNodeVisibilityProviderStrategy... and I think I've done that below... But I'm no Autofac ninja.
In MvcSiteMapProviderModule.cs, I added the new assembly everywhere I could think...
string[] includeAssembliesForScan = new string[] { "MyOldAssembly", "MyNewAssembly" };
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly, typeof(MyWasWorkingVisibilityProvider).Assembly };
builder.RegisterType<SiteMapNodeVisibilityProviderStrategy>()
.As<ISiteMapNodeVisibilityProviderStrategy>()
.WithParameters(new List<Parameter> { new NamedParameter("defaultProviderName", string.Empty), new NamedParameter("siteMapNodeVisibilityProviders", new [] { new MyWasWorkingVisibilityProvider() }) });
builder.RegisterType<MyWasWorkingVisibilityProvider>()
.As<ISiteMapNodeVisibilityProvider>();
But it still doesn't work.
For what it's worth, the visibility provider for any specific menu item is configured in the database, and the entire menu structure is loaded with a dynamic node provider that is also now in the same assembly as where I've moved the visibility providers. The dynamic node provider is obviously working because it's getting all the way to the point where it's trying to load visibility providers.
I thought https://github.com/maartenba/MvcSiteMapProvider/issues/237 looked helpful, I couldn't get the visibility provider-specific code to compile..
Another example that didn't have any effect: MVC Site Map Provider - SiteMapPath Performance Very Slow?
So I'm stuck now. I'm not a wizard with Autofac OR MvcSiteMap provider, but, like I said, everything was working fine until I moved the visibility provider to another assembly.
Thanks very much for your time and attention! I'm frustrated at this point.

Just a hunch, but I suspect that you didn't update all of the visibilityProvider="MyNamespace.MyVisibilityProvider, MyAssembly" references in your configuration to the new assembly. The SiteMapNodeVisibilityProviderBase uses the .NET full type name to locate the correct type, including the assembly name.
// From MvcSiteMapProvider.SiteMapNodeVisibilityProviderBase
public virtual bool AppliesTo(string providerName)
{
if (string.IsNullOrEmpty(providerName))
return false;
return this.GetType().Equals(Type.GetType(providerName, false));
}
As for the DI registration, provided you left the first call to CommonConventions.RegisterAllImplementationsOfInterface() in place, you had it right with this line:
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly, typeof(MyWasWorkingVisibilityProvider).Assembly };
So the code should look something like this:
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly, typeof(MyWasWorkingVisibilityProvider).Assembly };
var excludeTypes = new Type[] {
// Use this array to add types you wish to explicitly exclude from convention-based
// auto-registration. By default all types that either match I[TypeName] = [TypeName] or
// I[TypeName] = [TypeName]Adapter will be automatically wired up as long as they don't
// have the [ExcludeFromAutoRegistrationAttribute].
//
// If you want to override a type that follows the convention, you should add the name
// of either the implementation name or the interface that it inherits to this list and
// add your manual registration code below. This will prevent duplicate registrations
// of the types from occurring.
// Example:
// typeof(SiteMap),
// typeof(SiteMapNodeVisibilityProviderStrategy)
};
var multipleImplementationTypes = new Type[] {
typeof(ISiteMapNodeUrlResolver),
typeof(ISiteMapNodeVisibilityProvider),
typeof(IDynamicNodeProvider)
};
// Matching type name (I[TypeName] = [TypeName]) or matching type name + suffix Adapter (I[TypeName] = [TypeName]Adapter)
// and not decorated with the [ExcludeFromAutoRegistrationAttribute].
CommonConventions.RegisterDefaultConventions(
(interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).SingleInstance(),
new Assembly[] { siteMapProviderAssembly },
allAssemblies,
excludeTypes,
string.Empty);
// Multiple implementations of strategy based extension points (and not decorated with [ExcludeFromAutoRegistrationAttribute]).
CommonConventions.RegisterAllImplementationsOfInterface(
(interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).SingleInstance(),
multipleImplementationTypes,
allAssemblies,
excludeTypes,
string.Empty);
// Registration of internal controllers
CommonConventions.RegisterAllImplementationsOfInterface(
(interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).AsSelf().InstancePerDependency(),
new Type[] { typeof(IController) },
new Assembly[] { siteMapProviderAssembly },
new Type[0],
string.Empty);
So, in short your DI configuration is right, but your node configuration of the VisibilityProvider attribute/property is not.
NOTE: The below line is only for scanning for the [MvcSiteMapNode] attribute on controllers that may not be in the same project as MvcSiteMapProvider, and has nothing to do with the setup of visibility providers.
string[] includeAssembliesForScan = new string[] { "MyOldAssembly", "MyNewAssembly" };

Related

IoC container that doesn't require registration

Is there such a thing?
If all I need to do is resolve IThing to Thing, why do I need to even create a registration? I should just map dynamically during run-time. I can easily create one with reflection..but was hoping to avoid building my own..
I am resolving like for like. It is easy to do with reflection, without registration..
What you're looking for is the concept of Auto-Registration. Most containers allow you to either register types in an assembly based on a convention, or do unregistered type resolution and find the missing type for you.
For instance, you can search through an assembly and register all types that match the convention during startup:
var container = new Container();
Assembly assembly = typeof(Thing).Assembly;
var mappings =
from type in assembly.GetExportedTypes()
let matchingInterface = "I" + type.Name
let service = type.GetInterfaces().FirstOrDefault(i => matchingInterface)
where service != null
select new { service, type };
foreach (var mapping in mappings)
{
// Register the type in the container
container.Register(mapping.service, mapping.type);
}
Using unregistered type resolution, you can make the registration on the fly. How to do this is very much dependent on which container you use. With Simple Injector, for instance, this looks as follows:
var container = new Container();
Assembly assembly = typeof(Thing).Assembly;
container.ResolveUnregisteredType += (s, e)
{
Type service = e.UnregisteredServiceType;
if (service.IsInterface)
{
var types = (
from type in asssembly.GetExportedTypes()
where service.IsAssignableFrom(type)
where !type.IsAbstract
where service.Name == "I" + type.Name
select type)
.ToArray();
if (types.Length == 1)
{
e.Register(Lifestyle.Transient.CreateRegistration(types[0], container));
}
}
};
Both methods prevent you from having to update your container configuration constantly.

Breezejs EntityManager MetadataStore and fetchEntityByKey

I have a SPA application (durandaljs), and I have a specific route where I map the "id" of the entity that I want to fetch.
The template is "/#/todoDetail/:id".
For example, "/#/todoDetail/232" or "/#/todoDetail/19".
On the activate function of viewmodel, I get the route info so I can grab the id. Then I create a new instance of breezejs EntityManager to get the entity with the given id.
The problem is when I call manager.fetchEntityByKey("Todos", id), the EntityManager doesn't have yet the metadata from the server, so it throwing exception "Unable to locate an 'Type' by the name: Todos".
It only works if first I execute a query against the store (manager.executeQuery), prior to calling fetchEntityByKey.
Is this an expected behavior or a bug ? Is there any way to auto-fecth the metadata during instantiation of EntityManager ?
note: I believe it's hard to use a shared EntityManager in my case, because I want to allow the user directly type the route on the browser.
EDIT: As a temporary workaround, I'm doing this:
BreezeService.prototype.get = function (id, callback) {
var self = this;
function queryFailed(error) {
app.showMessage(error.message);
callback({});
}
/* first checking if metadatastore was already loaded */
if (self.manager.metadataStore.isEmpty()) {
return self.manager.fetchMetadata()
.then(function (rawMetadata) {
return executeQuery();
}).fail(queryFailed);
} else {
return executeQuery();
}
/* Now I can fetch */
function executeQuery() {
return self.manager.fetchEntityByKey(self.entityType, id, true)
.then(callback)
.fail(queryFailed);
}
};
You've learned about fetchMetadata. That's important. If you application can begin without issuing a query, you have to use fetchMetadata and wait for it to return before you can perform any operations directly on the cache (e.g., checking for an entity by key in the cache before falling back to a database query).
But I sense something else going on because you mentioned multiple managers. By default a new manager doesn't know the metadata from any other manager. But did you know that you can share a single metadataStore among managers? You can.
What I often do (and you'll see it in the metadata tests in the DocCode sample), is get a metadataStore for the application, write an EntityManager factory function that creates new managers with that metadataStore, and then use the factory whenever I'm making new managers ... as you seem to be doing when you spin up a ViewModel to review the TodoDetail.
Coming from a Silverlight background where I used a lot of WCF RIA Services combined with Caliburn Micro, I used this approach for integrating Breeze with Durandal.
I created a sub folder called services in the App folder of the application. In that folder I created a javascript file called datacontext.js. Here is a subset of my datacontext:
define(function (require) {
var breeze = require('lib/breeze'); // path to breeze
var app = require('durandal/app'); // path to durandal
breeze.NamingConvention.camelCase.setAsDefault();
// service name is route to the Web API controller
var serviceName = 'api/TeamData',
// manager is the service gateway and cache holder
manager = new breeze.EntityManager(serviceName),
store = manager.metadataStore;
function queryFailed(error) {
app.showMessage("Query failed: " + error.message);
}
// constructor overrides here
// included one example query here
return datacontext = {
getSponsors: function (queryCompleted) {
var query = breeze.EntityQuery.from("Sponsors");
return manager
.executeQuery(query)
.then(queryCompleted)
.fail(queryFailed)
}
};
}
Then in your durandal view models you can just require the services/datacontext. For example, here is part of a sample view model from my app:
define(function (require) {
var datacontext = require('services/datacontext');
var ctor = function () {
this.displayName = 'Sponsors',
this.sponsors = ko.observable(false)
};
ctor.prototype.activate = function () {
var that = this;
return datacontext.getSponsors(function (data) { that.sponsors(data.results) });
}
return ctor;
});
This will allow you to not worry about initializing the metadata store in every view model since it is all done in one place.

NewtonSoft json Contract Resolver with MVC 4.0 Web Api not producing the output as expected

I am trying to create a conditional ContractResolver so that I can control the serialization differently depending on the web request/controller action.
For example in my User Controller I want to serialize all properties of my User but some of the related objects I might only serialize the primitive types. But if I went to my company controller I want to serialize all the properties of the company but maybe only the primitive ones of the user (because of this I don't want to use dataannotations or shouldserialize functions.
So looking at the custom ContractResolver page i created my own.
http://james.newtonking.com/projects/json/help/index.html?topic=html/ContractResolver.htm
It looks like this
public class IgnoreListContractResolver : DefaultContractResolver
{
private readonly Dictionary<string, List<string>> IgnoreList;
public IgnoreListContractResolver(Dictionary<string, List<string>> i)
{
IgnoreList = i;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
if(IgnoreList.ContainsKey(type.Name))
{
properties.RemoveAll(x => IgnoreList[type.Name].Contains(x.PropertyName));
}
return properties;
}
}
And then in my web api controller action for GetUsers i do this
public dynamic GetUsers()
{
List<User> Users = db.Users.ToList();
List<string> RoleList = new List<string>();
RoleList.Add("UsersInRole");
List<string> CompanyList = new List<string>();
CompanyList.Add("CompanyAccesses");
CompanyList.Add("ArchivedMemberships");
CompanyList.Add("AddCodes");
Dictionary<string, List<string>> IgnoreList = new Dictionary<string, List<string>>();
IgnoreList.Add("Role", RoleList);
IgnoreList.Add("Company", CompanyList);
GlobalConfiguration
.Configuration
.Formatters.JsonFormatter
.SerializerSettings
.ContractResolver = new IgnoreListContractResolver(IgnoreList);
return new { List = Users, Status = "Success" };
}
So when debugging this I see my contract resolver run and it returns the correct properties but the Json returned to the browser still contains entries for the properties I removed from the list.
Any ideas what I am missing or how I can step into the Json serialization step in webapi controllers.
*UPDATE**
I should add that this is in an MVC4 project that has both MVC controllers and webapi controllers. The User, Company, and Role objects are objects (created by code first) that get loaded from EF5. The controller in question is a web api controller. Not sure why this matters but I tried this in a clean WebApi project (and without EF5) instead of an MVC project and it worked as expected. Does that help identify where the problem might be?
Thanks
*UPDATE 2**
In the same MVC4 project I created an extension method for the Object class which is called ToJson. It uses Newtonsoft.Json.JsonSerializer to serialize my entities. Its this simple.
public static string ToJson(this object o, Dictionary<string, List<string>> IgnoreList)
{
JsonSerializer js = JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings()
{
Formatting = Formatting.Indented,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
ContractResolver = new IgnoreListContractResolver(IgnoreList),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
js.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
var jw = new StringWriter();
js.Serialize(jw, o);
return jw.ToString();
}
And then in an MVC action i create a json string like this.
model.jsonUserList = db.Users.ToList().ToJson(IgnoreList);
Where the ignore list is created exactly like my previous post. Again I see the contract resolver run and correctly limit the properties list but the output json string still contains everything (including the properties I removed from the list). Does this help? I must be doing something wrong and now it seems like it isn't the MVC or web api framework. Could this have anything to do with EF interactions/ proxies /etc. Any ideas would be much appreciated.
Thanks
*UPDATE 3***
Process of elimination and a little more thorough debugging made me realize that EF 5 dynamic proxies were messing up my serialization and ContractResolver check for the type name match. So here is my updated IgnoreListContractResolver. At this point I am just looking for opinions on better ways or if I am doing something terrible. I know this is jumping through a lot of hoops just to use my EF objects directly instead of DTOs but in the end I am finding this solution is really flexible.
public class IgnoreListContractResolver : CamelCasePropertyNamesContractResolver
{
private readonly Dictionary<string, List<string>> IgnoreList;
public IgnoreListContractResolver(Dictionary<string, List<string>> i)
{
IgnoreList = i;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
string typename = type.Name;
if(type.FullName.Contains("System.Data.Entity.DynamicProxies.")) {
typename = type.FullName.Replace("System.Data.Entity.DynamicProxies.", "");
typename = typename.Remove(typename.IndexOf('_'));
}
if (IgnoreList.ContainsKey(typename))
{
//remove anything in the ignore list and ignore case because we are using camel case for json
properties.RemoveAll(x => IgnoreList[typename].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
}
return properties;
}
}
I think it might help if you used Type instead of string for the ignore list's key type. So you can avoid naming issues (multiple types with the same name in different namespaces) and you can make use of inheritance. I'm not familiar with EF5 and the proxies, but I guess that the proxy classes derive from your entity classes. So you can check Type.IsAssignableFrom() instead of just checking whether typename is a key in the ignore list.
private readonly Dictionary<Type, List<string>> IgnoreList;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
// look for the first dictionary entry whose key is a superclass of "type"
Type key = IgnoreList.Keys.FirstOrDefault(k => k.IsAssignableFrom(type));
if (key != null)
{
//remove anything in the ignore list and ignore case because we are using camel case for json
properties.RemoveAll(x => IgnoreList[key].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
}
return properties;
}
Then the ignore list must be created like this (I also used the short syntax for creating the list and dictionary):
var CompanyList = new List<string> {
"CompanyAccesses",
"ArchivedMemberships",
"AddCodes"
};
var IgnoreList = new Dictionary<Type, List<string>> {
// I just replaced "Company" with typeof(Company) here:
{ typeof(Company), CompanyList }
};
Be aware that, if you use my code above, adding typeof(object) as the first key to the ignore list will cause this entry to be matched every time, and none of your other entries will ever be used! This happens because a variable of type object is assignable from every other type.

2 razor partial views with the same name in different projects using razorgenerator

In our project we're using the razorgenerator of David Ebbo. This allowed us to move some of our cshtml files to a class library.
What we would like to achieve now is the following:
MyCommonViews has a "MyView.cshtml" in its Views folder.
MyWebProject ALSO has a "MyView.cshtml" in its Views folder.
MyOtherWebProject DOES NOT have a "MyView.cshtml" in its Views folder.
When MyOtherWebProject needs to load MyView.cshtml, it will pick the one which is in the compiled MyCommonViews project. That is what we want.
BUT when MyWebProject needs to load MyView.cshtml, we would like it to pick up the "overridden" MyView.cshtml file which is in the MyWebProject itself.
Is what we want possible and how?
Manu.
I wrote up a hacky solution for our problem. It hacks into the razorgenerators viewengine and removes all appropriate entries from the (private readonly) Dictionary it has.
The code is ran on application start.
Talk is cheap, show me the code:
private static void HackRazorGeneratorToAllowViewOverriding()
{
// first we search for the PrecompiledMvcEngine
var razorGeneratorViewEngine = ViewEngines.Engines.ToList().FirstOrDefault(ve => ve.GetType().Name.Contains("PrecompiledMvcEngine"));
if (razorGeneratorViewEngine == null)
return;
// retrieve the dictionary where it keeps the mapping between a view path and the (view object) type to instantiate
var razorMappings = (IDictionary<string, Type>)ReflectionUtils.GetPrivateReadonly("_mappings", razorGeneratorViewEngine);
// retrieve a list of all our cshtml files in our 'concrete' web project
var files = Directory.GetFiles(Path.Combine(WebConfigSettings.RootPath, "Views"), "*.cshtml", SearchOption.AllDirectories);
// do some kungfu on those file paths so that they are in the same format as in the razor mapping dictionary
var concreteViewPaths = files.Select(fp => string.Format("~{0}", fp.Replace(WebConfigSettings.RootPath, "").Replace(#"\", "/"))).ToList();
// loop through each of the cshtml paths (of our 'concrete' project) and remove it from the razor mappings if it's there
concreteViewPaths.ForEach(vp =>
{
if (razorMappings.ContainsKey(vp))
razorMappings.Remove(vp);
});
}
WebConfigSettings.RootPath contains the path on HD to the root of our web application.
This is a part of our static ReflectionUtils class:
/// <summary>
/// Get a field that is 'private readonly'.
/// </summary>
public static object GetPrivateReadonly(string readonlyPropName, object instance)
{
var field = instance.GetType().GetField(readonlyPropName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
throw new NullReferenceException(string.Format("private readonly field '{0}' not found in '{1}'", readonlyPropName, instance));
return field.GetValue(instance);
}
This did the trick. We basically force the PrecompiledMvcEngine to "forget" any view which we have in our concrete project.
You can also try CompositePrecompiledMvcEngine from RazorGenerator.Mvc 2.1.0. It was designed for correct support of view overriding within multiple assemblies. Piece of code:
var engine = new CompositePrecompiledMvcEngine(
/*1*/ PrecompiledViewAssembly.OfType<MyCommonViewsSomeClass>(),
/*2*/ PrecompiledViewAssembly.OfType<MyWebProjectSomeClass>(
usePhysicalViewsIfNewer: HttpContext.Current.IsDebuggingEnabled));
ViewEngines.Engines.Insert(0, engine);
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
The first line will register all views from the MyCommonViews assembly (~/Views/MyView.cshtml), the second line will register all views from the MyWebProject or MyOtherWebProject assembly.
When it encounters the virtual path, that already has been registered (~/Views/MyView.cshtml from the MyWebProject assembly), it overrides an old mapping with a new view type mapping.
If another project doesn't has view with the same virtual path (MyOtherWebProject) it leaves source mapping unchanged.
There is flag PreemptPhysicalFiles = false which does the magic.
Full sample:
[assembly: WebActivator.PostApplicationStartMethod(typeof(Application.Web.Common.App_Start.RazorGeneratorMvcStart), "Start")]
namespace Application.Web.Common.App_Start
{
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine2(typeof (RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = true, //compile if file changed
PreemptPhysicalFiles = false //use local file if exist
};
ViewEngines.Engines.Add(engine);//Insert(0,engine) ignores local partial views
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
}
However there is maybe a small bug:
http://razorgenerator.codeplex.com/workitem/100

MasterLocationFormats in WebFormViewEngine not used?

I tried to make the ViewEngine use an additional path using:
base.MasterLocationFormats = new string[] {
"~/Views/AddedMaster.Master"
};
in the constructor of the ViewEngine. It works well for aspx and ascx(PartialViewLocationFormats, ViewLocationFormats).
I still have to supply the MasterPage in web.config or in the page declaration. But if I do, then this declaration is used, not the one in the ViewEngine.
If I use am empty MasterLocationFormats, no error is thrown. Is this not implemeted in RC1?
EDIT:
using:
return View("Index", "AddedMaster");
instead of
return View("Index");
in the Controller worked.
Your example isn't really complete, but I am going to guess that your block of code exists at the class level and not inside of a constructor method. The problem with that is that the base class (WebFormViewEngine) initializes the "location format" properties in a constructor, hence overriding your declaration;
public CustomViewEngine()
{
MasterLocationFormats = new string[] {
"~/Views/AddedMaster.Master"
};
}
If you want the hard-coded master to only kick in as a sort of last effort default, you can do something like this:
public CustomViewEngine()
{
MasterLocationFormats = new List<string>(MasterLocationFormats) {
"~/Views/AddedMaster.Master"
}.ToArray();
}

Resources