ASP.net core MVC 6 Data Annotations separation of concerns - data-annotations

I want put the Data Annotations Attribute and the IClientValidatable interface in two seperate assemblies to have separation of concerns. One is called Common and the other Comman.Web.
These links explain how it works in MVC 5:
Keeping IClientValidatable outside the model layer
http://www.eidias.com/blog/2012/5/25/mvc-custom-validator-with-client-side-validation
Unfortunately in MVC 6 there is no
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(MyValidationAttribute),
typeof(MyValidationAttributeAdapter)
);
How does it work in ASP.net core MVC 6? I use the RC1.

In Startup.cs, in the ConfigureServices method:
services.AddMvc(options =>
{
options.ModelValidatorProviders.Insert(0, new CustomModelValidatorProvider());
});
You have to adjust your code as the ASP.NET Core 1.0 API is changed. You could find a sample implementation in the asp.net repo: DataAnnotationsModelValidatorProvider.cs

In ASP.net core 1.0 I was able to do this by replacing the IValidationAttributeAdapterProvider service.
public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
public IValidationAttributeAdapterProvider internalImpl;
public CustomValidationAttributeAdapterProvider()
{
internalImpl = new ValidationAttributeAdapterProvider();
}
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
IAttributeAdapter adapter = internalImpl.GetAttributeAdapter(attribute, stringLocalizer);
if (adapter == null)
{
var type = attribute.GetType();
if (type == typeof(CustomValidatorAttribute))
{
adapter = new CustomNumberValidatorAdapter((CustomValidatorAttribute)attribute, stringLocalizer);
}
}
return adapter;
}
}
In Startup ConfigureServices
if (services.Any(f => f.ServiceType == typeof(IValidationAttributeAdapterProvider)))
{
services.Remove(services.Single(f => f.ServiceType == typeof(IValidationAttributeAdapterProvider)));
}
services.AddScoped<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Related

Disable entity framework proxy creation and lazy loading from another project

I have multiple projects referencing the a database project in the same solution and it all works great. I have recently added an mvc web api project to the collection and its retrieving data using the same business services.
I would like to disable lazy loading and proxy creation only in the instance of the web api. Is there a way i can do this global like in the application start in Gblobal asax.
XYZContext.Configuration.ProxyCreationEnabled = false
XYZContext.Configuration.LazyLoadingEnabled = false;
I'd suggest creating separate context for the API with settings initialized to required values
public class XYZContext : DbContext
{
}
public class XYZContextWithoutLazyLoadingAndProxies : XYZContext
{
public XYZContextWithoutLazyLoadingAndProxies()
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
}
Or use configuration values from web.config in constructor
public class XYZContext : DbContext
{
public XYZContext()
{
if (ConfigurationManager.AppSettings["LazyLoading"]?.ToString() == "false")
{
Configuration.LazyLoadingEnabled = false;
}
}
}

RouteData not applied to HttpContext only on .NET Core 3

I want to set RouteData but changes are not applied to HttpContext.
public class LocalizeRouteAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
context.RouteData.Values["culture"] = "en";
string routeCulture = context.HttpContext.GetRouteValue("culture")?.ToString();
// ON NET CORE 2.2 routeCulture return "en"
// ON NET CORE 3.1 routeCulture return null
base.OnActionExecuting(context);
}
}
Am I missing something new?
To reproduce this case you have to create the filter on a new MVC simple project and apply it to HomeController. It will work on 2.2 but not on 3.1.

Add Controls Dynamically in ASP .NET Core

I've tried to follow the answer by Darin Dimitrov on this post:
How to create controls dynamically in MVC 3 based on an XML file. But in ASP .NET Core (Specifically ASP .NET Core 2) there is no DefaultModelBinder so I have to modify the Custom Model Binder code in order to work with ASP .NET Core.
Actually my code is:
public class ControlModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type");
object model = null;
switch (type.FirstValue)
{
case "TextBox":
{
model = new TextBoxViewModel();
break;
}
case "CheckBox":
{
model = new CheckBoxViewModel();
break;
}
case "DropDownList":
{
model = new DropDownListViewModel();
break;
}
default:
{
throw new NotImplementedException();
}
};
bindingContext.ModelMetadata = ModelMetadataProvider.GetMetadataForType(model.GetType());
bindingContext.Model = model;
return Task.CompletedTask;
}
}
The problem that I'm having actually is that ModelMetadataProvider.GetMetadataForType is showing me the error:
It's required a reference for a non-static field, method or property
'ModelMetadataProviderGetMetadataForType(Type)'.
So, What can I do in order to make work the model.GetType()?
How I can get ControllerContext and modelType?
If someone knows how to convert this piece of code from ASP .NET MVC to ASP .NET Core I would really apreciate his help.
Thank you in advance!

How can I get started with ASP.NET (5) Core and Castle Windsor for Dependency Injection?

Background:
I've used Castle Windsor with Installers and Facilities according to the Castle Windsor tutorial with earlier versions of MVC (pre-6) and WebAPI.
ASP.NET (5) Core has included some Dependency Injection support but I still haven't figured out exactly how to wire it up, and the few samples I have found look a lot different than how I've used it before (with the installers/facilities). Most examples predate ASP.NET (5) cores recent release and some seem to have outdated information.
It seems to have changed quite radically from the previous versions composition root setup, and not even Microsoft.Framework.DependencyInjection.ServiceProvider can resolve all of the dependencies when I set it as the Castle Windsor DI fallback. I'm still digging into the details but there isn't much up to date information.
My attempt to use Castle Windsor for DI
I've found an adapter like this: Github Castle.Windsor DI container.
Startup.cs
private static IWindsorContainer container;
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
container = new WindsorContainer();
app.UseServices(services =>
{
// ADDED app.ApplicationServices FOR FALLBACK DI
container.Populate(services, app.ApplicationServices);
container.BeginScope();
return container.Resolve<IServiceProvider>();
});
// ... default stuff
WindsorRegistration.cs
I added a few lines to add a Castle Windsor ILazyComponentLoader fallback.
using Castle.MicroKernel.Lifestyle;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Resolvers.SpecializedResolvers;
using Castle.Windsor;
using Microsoft.Framework.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Notes.Infrastructure
{
/// <summary>
/// An adapted current autofac code to work with Castle.Windsor container.
/// https://github.com/aspnet/Home/issues/263
/// </summary>
public static class WindsorRegistration
{
public static void Populate(
this IWindsorContainer container,
IEnumerable<IServiceDescriptor> descriptors,
IServiceProvider fallbackProvider // ADDED FOR FALLBACK DI
)
{
// ADDED FOR FALLBACK DI
// http://davidzych.com/2014/08/27/building-the-castle-windsor-dependency-injection-populator-for-asp-net-vnext/
// Trying to add a fallback if Castle Windsor doesn't find the .NET stuff
var fallbackComponentLoader = new FallbackLazyComponentLoader(fallbackProvider);
container.Register(Component.For<ILazyComponentLoader>().Instance(fallbackComponentLoader));
// Rest as usual from the Github link
container.Register(Component.For<IWindsorContainer>().Instance(container));
container.Register(Component.For<IServiceProvider>().ImplementedBy<WindsorServiceProvider>());
container.Register(Component.For<IServiceScopeFactory>().ImplementedBy<WindsorServiceScopeFactory>());
container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));
Register(container, descriptors);
}
private static void Register(
IWindsorContainer container,
IEnumerable<IServiceDescriptor> descriptors)
{
foreach (var descriptor in descriptors)
{
if (descriptor.ImplementationType != null)
{
// Test if the an open generic type is being registered
var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
if (serviceTypeInfo.IsGenericTypeDefinition)
{
container.Register(Component.For(descriptor.ServiceType)
.ImplementedBy(descriptor.ImplementationType)
.ConfigureLifecycle(descriptor.Lifecycle)
.OnlyNewServices());
}
else
{
container.Register(Component.For(descriptor.ServiceType)
.ImplementedBy(descriptor.ImplementationType)
.ConfigureLifecycle(descriptor.Lifecycle)
.OnlyNewServices());
}
}
else if (descriptor.ImplementationFactory != null)
{
var service1 = descriptor;
container.Register(Component.For(descriptor.ServiceType)
.UsingFactoryMethod<object>(c =>
{
var builderProvider = container.Resolve<IServiceProvider>();
return
service1.ImplementationFactory(builderProvider);
})
.ConfigureLifecycle(descriptor.Lifecycle)
.OnlyNewServices());
}
else
{
container.Register(Component.For(descriptor.ServiceType)
.Instance(descriptor.ImplementationInstance)
.ConfigureLifecycle(descriptor.Lifecycle)
.OnlyNewServices());
}
}
}
private static ComponentRegistration<object> ConfigureLifecycle(
this ComponentRegistration<object> registrationBuilder,
LifecycleKind lifecycleKind)
{
switch (lifecycleKind)
{
case LifecycleKind.Singleton:
registrationBuilder.LifestyleSingleton();
break;
case LifecycleKind.Scoped:
registrationBuilder.LifestyleScoped();
break;
case LifecycleKind.Transient:
registrationBuilder.LifestyleTransient();
break;
}
return registrationBuilder;
}
private class WindsorServiceProvider : IServiceProvider
{
private readonly IWindsorContainer _container;
public WindsorServiceProvider(IWindsorContainer container)
{
_container = container;
}
public object GetService(Type serviceType)
{
return _container.Resolve(serviceType);
}
}
private class WindsorServiceScopeFactory : IServiceScopeFactory
{
private readonly IWindsorContainer _container;
public WindsorServiceScopeFactory(IWindsorContainer container)
{
_container = container;
}
public IServiceScope CreateScope()
{
return new WindsorServiceScope(_container);
}
}
private class WindsorServiceScope : IServiceScope
{
private readonly IServiceProvider _serviceProvider;
private readonly IDisposable _scope;
public WindsorServiceScope(IWindsorContainer container)
{
_scope = container.BeginScope();
_serviceProvider = container.Resolve<IServiceProvider>();
}
public IServiceProvider ServiceProvider
{
get { return _serviceProvider; }
}
public void Dispose()
{
_scope.Dispose();
}
}
}
}
First hiccup and resolution attempt
From that example I was getting:
An exception of type 'Castle.MicroKernel.ComponentNotFoundException' occurred in Castle.Windsor.dll but was not handled in user code
Additional information: No component for supporting the service Microsoft.Framework.Runtime.IAssemblyLoaderEngine was found
It wasn't available looking in the debugger at the Castle Fallback - Microsoft.Framework.DependencyInjection.ServiceProvider (table of services).
From http://davidzych.com/tag/castle-windsor/ I have tried to add a Fallback since Windsor couldn't resolve all of the ASP.NET dependencies.
FallbackLazyComponentLoader.cs
/// <summary>
/// https://github.com/davezych/DependencyInjection/blob/windsor/src/Microsoft.Framework.DependencyInjection.Windsor/FallbackLazyComponentLoader.cs
/// </summary>
public class FallbackLazyComponentLoader : ILazyComponentLoader
{
private IServiceProvider _fallbackProvider;
public FallbackLazyComponentLoader(IServiceProvider provider)
{
_fallbackProvider = provider;
}
public IRegistration Load(string name, Type service, IDictionary arguments)
{
var serviceFromFallback = _fallbackProvider.GetService(service);
if (serviceFromFallback != null)
{
return Component.For(service).Instance(serviceFromFallback);
}
return null;
}
}
It was seemingly necessary (to inject all the .NET dependencies)
I could comment out startup.cs app.UseBrowserLink(); to get rid of the IAssemblyLoaderEngine exception.
if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase))
{
//app.UseBrowserLink(); //
Now I run into an exception:
An exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll but was not handled in user code
Trying to get the service: {Name = "IUrlHelper" FullName = "Microsoft.AspNet.Mvc.IUrlHelper"}
public IRegistration Load(string name, Type service, IDictionary arguments)
{
var serviceFromFallback = _fallbackProvider.GetService(service);
How to move forward?
What is wrong with this attempt to wire up Castle Windsor DI into ASP.NET (5) Core?
For now I don't think you can use Castle Windsor Container as the DI container because Windsor doesn't support the new DNVM. But AutoFac does and they follow the same rule.
In the Startup.cs there is a ConfigureServices method whose return type is void. You can change the return type to ISerivceProvider and return a concrete IServiceProvider, the system will use the new IServiceProvider as the default DI container. Below is the AutoFac example.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
services.AddMvc();
var builder = new ContainerBuilder();
AutofacRegistration.Populate(builder, services);
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
The other DI adapters also implemented the similar interfaces. You can try yourself, but note AutoFac is in beta5 now so you need to make some adjustment to make your application run.
Hope this helps
There is a lot going on in your question, and to be honest I don't understand all of it.
However, there is a working Castle Windsor composition root in MvcSiteMapProvider that you are welcome reverse-engineer. Follow these steps to get a working composition root demo project for Windsor:
Create a new MVC 5 project.
Install MvcSiteMapProvider.MVC5.DI.Windsor.
Analyze the following files for the basic structure:
/App_Start/DIConfig.cs
/App_Start/CompositionRoot.cs
/DI/InjectableControllerFactory.cs
/DI/Windsor/WindsorDependencyInjectionContainer.cs
/DI/Windsor/Installers/MvcInstaller.cs
/DI/Windsor/Installers/MvcSiteMapProviderInstaller.cs
Once you have this working configuration, you can then refactor it and add to it to suit your application's needs.
As I recall, there weren't any changes required to make the MVC 4 DI configuration work with MVC 5. So, the problem you are running into is most likely one of the following:
You are using a 3rd party DI component that is not compatible with MVC 5.
You are using DependencyResolver, and your configuration doesn't include the necessary code to resolve the dependencies of MVC 5.
You are using advanced features of Castle Windsor that we are not using, and have them misconfigured in some way.
ControllerFactory vs DependencyResolver
Do note that according to Dependency Injection in .NET by Mark Seemann (which I highly recommend), it is ill-advised to use IDependencyResolver with Castle Windsor because it guarantees resource leaks. In fact, this is probably the most compelling argument that he makes for his reasoning for declaring service locator as anti-pattern.
The recommended approach is to use IControllerFactory as the integration point into MVC, which implements a ReleaseController method to solve this issue.
So looking at your code, literally all of it can be replaced by Castle.Windsor.MsDependencyInjection library.
Add Castle.Windsor.MsDependencyInjection to your project then use like so:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Normal component registration can go here...
return WindsorRegistrationHelper.CreateServiceProvider(yourWindsorContainer, services);
}

MapMvcAttributeRoutes: This method cannot be called during the application's pre-start initialization phase

I have a very simple test in a test project in a solution using ASP MVC V5 and attribute routing. Attribute routing and the MapMvcAttributeRoutes method are part of ASP MVC 5.
[Test]
public void HasRoutesInTable()
{
var routes = new RouteCollection();
routes.MapMvcAttributeRoutes();
Assert.That(routes.Count, Is.GreaterThan(0));
}
This results in:
System.InvalidOperationException :
This method cannot be called during the applications pre-start initialization phase.
Most of the answers to this error message involve configuring membership providers in the web.config file. This project has neither membership providers or a web.config file so the error seems be be occurring for some other reason. How do I move the code out of this "pre-start" state so that the tests can run?
The equivalent code for attributes on ApiController works fine after HttpConfiguration.EnsureInitialized() is called.
I recently upgraded my project to ASP.NET MVC 5 and experienced the exact same issue. When using dotPeek to investigate it, I discovered that there is an internal MapMvcAttributeRoutes extension method that has a IEnumerable<Type> as a parameter which expects a list of controller types. I created a new extension method that uses reflection and allows me to test my attribute-based routes:
public static class RouteCollectionExtensions
{
public static void MapMvcAttributeRoutesForTesting(this RouteCollection routes)
{
var controllers = (from t in typeof(HomeController).Assembly.GetExportedTypes()
where
t != null &&
t.IsPublic &&
t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
!t.IsAbstract &&
typeof(IController).IsAssignableFrom(t)
select t).ToList();
var mapMvcAttributeRoutesMethod = typeof(RouteCollectionAttributeRoutingExtensions)
.GetMethod(
"MapMvcAttributeRoutes",
BindingFlags.NonPublic | BindingFlags.Static,
null,
new Type[] { typeof(RouteCollection), typeof(IEnumerable<Type>) },
null);
mapMvcAttributeRoutesMethod.Invoke(null, new object[] { routes, controllers });
}
}
And here is how I use it:
public class HomeControllerRouteTests
{
[Fact]
public void RequestTo_Root_ShouldMapTo_HomeIndex()
{
// Arrange
var routes = new RouteCollection();
// Act - registers traditional routes and the new attribute-defined routes
RouteConfig.RegisterRoutes(routes);
routes.MapMvcAttributeRoutesForTesting();
// Assert - uses MvcRouteTester to test specific routes
routes.ShouldMap("~/").To<HomeController>(x => x.Index());
}
}
One problem now is that inside RouteConfig.RegisterRoutes(route) I cannot call routes.MapMvcAttributeRoutes() so I moved that call to my Global.asax file instead.
Another concern is that this solution is potentially fragile since the above method in RouteCollectionAttributeRoutingExtensions is internal and could be removed at any time. A proactive approach would be to check to see if the mapMvcAttributeRoutesMethod variable is null and provide an appropriate error/exceptionmessage if it is.
NOTE: This only works with ASP.NET MVC 5.0. There were significant changes to attribute routing in ASP.NET MVC 5.1 and the mapMvcAttributeRoutesMethod method was moved to an internal class.
In ASP.NET MVC 5.1 this functionality was moved into its own class called AttributeRoutingMapper.
(This is why one shouldn't rely on code hacking around in internal classes)
But this is the workaround for 5.1 (and up?):
public static void MapMvcAttributeRoutes(this RouteCollection routeCollection, Assembly controllerAssembly)
{
var controllerTypes = (from type in controllerAssembly.GetExportedTypes()
where
type != null && type.IsPublic
&& type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
&& !type.IsAbstract && typeof(IController).IsAssignableFrom(type)
select type).ToList();
var attributeRoutingAssembly = typeof(RouteCollectionAttributeRoutingExtensions).Assembly;
var attributeRoutingMapperType =
attributeRoutingAssembly.GetType("System.Web.Mvc.Routing.AttributeRoutingMapper");
var mapAttributeRoutesMethod = attributeRoutingMapperType.GetMethod(
"MapAttributeRoutes",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(RouteCollection), typeof(IEnumerable<Type>) },
null);
mapAttributeRoutesMethod.Invoke(null, new object[] { routeCollection, controllerTypes });
}
Well, it's really ugly and I'm not sure if it'll be worth the test complexity, but here's how you can do it without modifying your RouteConfig.Register code:
[TestClass]
public class MyTestClass
{
[TestMethod]
public void MyTestMethod()
{
// Move all files needed for this test into a subdirectory named bin.
Directory.CreateDirectory("bin");
foreach (var file in Directory.EnumerateFiles("."))
{
File.Copy(file, "bin\\" + file, overwrite: true);
}
// Create a new ASP.NET host for this directory (with all the binaries under the bin subdirectory); get a Remoting proxy to that app domain.
RouteProxy proxy = (RouteProxy)ApplicationHost.CreateApplicationHost(typeof(RouteProxy), "/", Environment.CurrentDirectory);
// Call into the other app domain to run route registration and get back the route count.
int count = proxy.RegisterRoutesAndGetCount();
Assert.IsTrue(count > 0);
}
private class RouteProxy : MarshalByRefObject
{
public int RegisterRoutesAndGetCount()
{
RouteCollection routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes); // or just call routes.MapMvcAttributeRoutes() if that's what you want, though I'm not sure why you'd re-test the framework code.
return routes.Count;
}
}
}
Mapping attribute routes needs to find all the controllers you're using to get their attributes, which requires accessing the build manager, which only apparently works in app domains created for ASP.NET.
What are you testing here? Looks like you are testing a 3rd party extension method. You shouldn't be using your unit tests to test 3rd party code.

Resources