I'm developing an ASP.NET WebApi2 (.NET 4.7.2) with Owin and Autofac as IOC container. The Dtos shall get validated with FluentValidation before reaching the controller. Here is my project configuration:
public class Startup
{
public void Configuration(IAppBuilder app)
{
...
var config = new HttpConfiguration();
config.Filters.Add(new ValidateDtoStateFilter());
...
FluentValidationModelValidatorProvider.Configure(config);
...
}
}
Validate dto state filter:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
throw new ValidationException(actionContext.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key.Split('.')[1].FromPascalToCamelCase(), er.ErrorMessage))));
}
}
}
Autofac builder configuration:
builder.RegisterAssemblyTypes(typeof(MyDtoValidator).Assembly)
.Where(t => t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
Autofac validator factory:
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext _context;
public AutofacValidatorFactory(IComponentContext context)
{
_context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
object instance;
return _context.TryResolve(validatorType, out instance) ? instance as IValidator : null;
}
}
It works fine with POST or PUT endpoints where the dto comes [FromBody] in the requets payload. It does not work with [FromUri] dtos, e.g. in a GET request. The validator will be created by Autofac, but in OnActionExecuting of ValidateDtoStateFilter actionContext.ModelState is always true, no matter if the supplied data is valid or not.
How can I achieve that [FromUri] dtos get validated by the FluentValidation middleware as well?
Update
The issue does not occur any more without my having changed anything.
I could not reproduce this behaviour. Tested with Autofac 6.0.0 and FluentValidation 8.6.1. However, both [FromUri] and [FromBody] dtos set actionContext.ModelState.IsValid to true when the model is null (either no query string parameters passed or request body is empty). In this case no errors are generated so the validation passes.
This is actually by design. The purpose of FluentValidation is to validate properties on objects, which by definition requires a non-null instance in order to work.
https://github.com/FluentValidation/FluentValidation/issues/486
I can think of two ways to work around this limitation.
In your controller, simply test the dto is null:
[HttpGet]
public IHttpActionResult Get([FromUri] MyDto dto)
{
if (dto == null) return BadRequest();
...
}
Update the ValidateDtoStateFilter class to manually trigger validation of null dtos that have validator defined:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
private readonly IDependencyResolver _resolver;
public ValidateDtoStateFilter(IDependencyResolver resolver)
{
_resolver = resolver;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
var nullDtosWithValidatorDefined = actionContext.ActionArguments
.Where(argument => argument.Value == null)
.Select(argument => new
{
argument.Key,
// you may need to account for p.IsOptional and p.DefaultValue here
Type = parameters.FirstOrDefault(p => p.ParameterName == argument.Key)?.ParameterType
})
.Where(argument => argument.Type != null)
.Select(argument => new {
argument.Key,
Validator = (IValidator)_resolver.GetService(typeof(IValidator<>).MakeGenericType(argument.Type))
})
.Where(argument => argument.Validator != null)
.Select(argument => argument.Key);
foreach (var dto in nullDtosWithValidatorDefined)
{
actionContext.ModelState.AddModelError(dto, $"'{dto}' must not be null.");
}
if (!actionContext.ModelState.IsValid)
{
var errors = actionContext
.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key, er.ErrorMessage))
);
throw new ValidationException(errors);
}
}
}
// in the Startup.cs file the ValidateDtoStateFilter must be added after the DependencyResolver is set to Autofac:
config.DependencyResolver = new AutofacWebApiDependencyResolver(ConfigureAutofac());
config.Filters.Add(new ValidateDtoStateFilter(config.DependencyResolver));
This may not answer your specific problem though, it would be easier if you shared a link to your project or a snippet of your Startup.cs file, as it could be something with your api configuration.
Related
I need an API that would expose all my DB tables as an OData API's. For this I have used scaffold db to generate all the existing models, and I have already managed to create a correct EDM model using reflection for the OData to work with. I have also created a generic controller that I can use as a base controller for each of my models. And it looks like this:
public class GenericController<TEntity> : ODataController
where TEntity : class, new()
{
private readonly DbContext _context;
public GenericController(DbContext context)
{
_context = context;
}
[HttpGet]
[EnableQuery(PageSize = 1000)]
[Route("odata/{Controller}")]
public async Task<ActionResult<IEnumerable<TEntity>>> GetObjects()
{
try
{
if (_context.Set<TEntity>() == null)
{
return NotFound();
}
var obj = await _context.Set<TEntity>().ToListAsync();
return Ok(obj);
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
}
I can use this controller as a base for each of my models by manually creating a controller per model like this:
[ApiController]
public class ModelController : GenericController<MyModel>
{
public ActiviteitenObjectsController(Dbcontext context) : base(context)
{
}
}
And it works fine with OData filters and everything. But the problem is I have way too many tables to be able to manually create the controllers for every single one of them.
I know you can use app.MapGet("/", () => "Hello World!") to map the endpoints to a delegate or even use HTTPContext inside of it, but I can't figure out how to use it in my case, so that it would work with OData as well. Are there any approaches I can use to solve my problem?
With reference from few links.
.Net Core Override Controller Route for Generic Controller
OData Navigation-Routing for a Generic-Controller with ODataQueryOptions-support
https://www.ben-morris.com/generic-controllers-in-asp-net-core/
Add Helper, GenericControllerNameAttribute & GenericControllerFeatureProvider.
Then decorate your controller with annotation [GenericControllerName].
Modify ConfigureServices from Startup and append .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new Controllers.GenericControllerFeatureProvider())); after services.AddMvc() or services.AddControllers() or services.AddControllersWithViews() whatever you have used.
Helper will initialize all the DBSet from your DbContext and create dictionary with key as name of Model and/or name of property.
Helper.cs
public static class Helper
{
public static Dictionary<string, System.Type> modelDictionary = new Dictionary<string, System.Type>(System.StringComparer.OrdinalIgnoreCase);
static Helper()
{
var properties = typeof(DbContext).GetProperties();
foreach (var property in properties)
{
var setType = property.PropertyType;
var isDbSet = setType.IsGenericType && (typeof(DbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()));
if (isDbSet)
{
// suppose you have DbSet as below
// public virtual DbSet<Activity> Activities { get; set; }
// genericType will be typeof(Activity)
var genericType = setType.GetGenericArguments()[0];
// Use genericType.Name if you want to use route as class name, i.e. /OData/Activity
if (!modelDictionary.ContainsKey(genericType.Name))
{
modelDictionary.Add(genericType.Name, genericType);
}
}
}
}
}
GenericControllerNameAttribute.cs
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericControllerNameAttribute : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() == typeof(Generic2Controller<>))
{
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name;
}
}
}
GenericControllerFeatureProvider
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
// Get the list of entities that we want to support for the generic controller
foreach (var entityType in Helper.modelDictionary.Values.Distinct())
{
var typeName = entityType.Name + "Controller";
// Check to see if there is a "real" controller for this class
if (!feature.Controllers.Any(t => t.Name == typeName))
{
// Create a generic controller for this type
var controllerType = typeof(Generic2Controller<>).MakeGenericType(entityType).GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}
GenericController.cs
[GenericControllerName]
public class GenericController<TEntity> : ODataController
where TEntity : class, new()
{
// Your code
}
Modification in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add .ConfigureApplicationPartManager(...); with AddMvc or AddControllers or AddControllersWithViews based on what you already have used.
services.AddMvc()
.ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
//services.AddControllers()
// .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
//services.AddControllersWithViews()
// .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new GenericControllerFeatureProvider()));
}
You can do something like below. Not using Generic controller as you have but its similar to that.
Add Helper & GenericController as below and modify Startup.cs.
Details
Helper will get all the DBSet from your DbContext and create dictionary with key as name of Model and/or name of property.
Modify your Startup.cs and add some code in Configure. Explanation is there in comment.
References
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#regular-expressions-in-constraints
How to get DbSet from entity name in EF Core / .NET Core 2.0
Helper.cs
public static class Helper
{
// Dictionary used to generate url pattern in Startup.cs & getting Entity for DbSet in GenericController.
public static Dictionary<string, System.Type> modelDictionary = new Dictionary<string, System.Type>(System.StringComparer.OrdinalIgnoreCase);
// Initialize dictionary and it will be use to generate URL pattern
static Helper()
{
var properties = typeof(DbContext).GetProperties();
foreach (var property in properties)
{
var setType = property.PropertyType;
var isDbSet = setType.IsGenericType && (typeof(DbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()));
if (isDbSet)
{
// Suppose you have DbSet as below
// public virtual DbSet<Activity> Activities { get; set; }
// genericType will be typeof(Activity)
var genericType = setType.GetGenericArguments()[0];
// Use genericType.Name if you want to use route as class name, i.e. /OData/Activity
if (!modelDictionary.ContainsKey(genericType.Name))
{
modelDictionary.Add(genericType.Name, genericType);
}
// Use property.Name if you want to use route as property name, i.e. /OData/Activities
if (!modelDictionary.ContainsKey(property.Name))
{
modelDictionary.Add(property.Name, genericType);
}
}
}
}
}
GenericController
public class GenericController : ODataController
{
private readonly DbContext _context;
public GenericController(DbContext context)
{
_context = context;
}
// GET: OData/{modelName}
public async Task<ActionResult<IEnumerable<Object>>> Get(string modelName)
{
// Using reflection get required DbSet and return the list
var entities = (IQueryable<object>)_context.GetType()
.GetMethod("Set", types: Type.EmptyTypes)
.MakeGenericMethod(Helper.modelDictionary[modelName])
.Invoke(_context, null);
var obj = await entities.ToListAsync();
return Ok(obj);
}
}
Modification in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// your code
// if you do not have app.UseEndpoints then add after app.UseRouting();
app.UseEndpoints(endpoints =>
{
// For reference for pattern matching and route binding check below link
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#regular-expressions-in-constraints
// Build Regex pattern for your generic controller and Map route accordingly for GenericController & action that we have added.
var pattern = "OData/{modelName:regex(" + string.Join('|', Controllers.Helper.modelDictionary.Keys) + ")}";
// Map controller action with pattern defined as "OData/{modelName:regex(Table1|Table2|...)}"
endpoints.MapControllerRoute(
name: "OData",
pattern: pattern,
defaults: new { controller = "Generic", action = "Get" });
});
// rest of your code
}
I have an Asp.net core 2 Web api. and I'm trying to implement a custom authorisation filter.
At the moment I have the following:
public class AuthorisationAttribute : TypeFilterAttribute
{
public AuthorisationAttribute() : base(typeof(AuthorisationFilter))
{
Arguments = new object[] { new Claim(ClaimTypes.UserData, "will be my user data") };
}
}
public class AuthorisationFilter : IAuthorizationFilter
{
readonly HttpContext _httpContext;
public AuthorisationFilter(HttpContext httpContext)
{
_httpContext = httpContext;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var authorisationCookie = context.HttpContext.Request.Headers.Where(t => t.Key == "auth").FirstOrDefault();
var temp = new JwtSecurityTokenHandler();
var unencryptedToken = temp.ReadToken(authorisationCookie.Value) as JwtSecurityToken;
var session = _httpContext.Session;
//MORE TO DO HERE YET! Just want to test getting called when expected.
return;
}
}
Then on a controller method I Have:
public class HomeController : Controller
{
[Authorisation(),HttpGet]
public IActionResult Index()
{
return View("~/Views/Home/Index.cshtml");
}
}
When I run the application The authorisationAttribute constructor gets called. At the point I try to call the controller Method I receive the following Error:
InvalidOperationException: A suitable constructor for type
AuthorisationFilter; could not be located. Ensure the type is concrete
and services are registered for all parameters of a public
constructor.
So in my startup.cs file I also added:
services.AddScoped<IAuthorizationFilter, AuthorisationFilter>();
but it's made no difference
The built-in DI does not know anything about the current HttpContext, first you have to add the IHttpContextAccessor to the service collection:
services.AddHttpContextAccessor();
Then you can get it as your filters constructor argument:
public AuthorisationFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
Then you can access to the current HttpContext via _httpContextAccessor.HttpContext.
However you can also access the current HttpContext through your AuthorizationFilterContext like you already use that in your sample code:
context.HttpContext
Edit: As you are setting the Argument property of the TypeFilterAttribute you have to make a constructor in your filter that uses that argument, so like:
public AuthorisationFilter(Claim claim)
{
}
I would like to require one policy for all actions on a controller, and I would like to also require a second policy for all calls to HTTP "edit methods" (POST, PUT, PATCH, and DELETE). That is, the edit methods should require both policies. Due to implementation requirements, and also a desire to keep the code DRY, I need the latter policy to be applied at the controller level, not duplicated on all the action methods.
As a simple example, I have a PeopleController, and I also have two permissions, implemented as Policies, ViewPeople and EditPeople. Right now I have:
[Authorize("ViewPeople")]
public class PeopleController : Controller { }
How do I go about adding the EditPeople policy/permission such that it "stacks" and only applies to the edit verbs?
I've run into two problems which both seem to be a real pain:
You can't have more than one AuthorizeAttribute or more than one Policy specified within the AuthorizeAttribute, AFAIK.
You can't access the Request in a custom AuthorizationHandler, so I can't check the HttpMethod to check it.
I tried working around the former with a custom Requirement and AuthorizationHandler, like so:
public class ViewEditRolesRequirement : IAuthorizationRequirement
{
public ViewEditRolesRequirement(Roles[] editRoles, Roles[] viewRoles)
=> (EditRoles, ViewRoles) = (editRoles, viewRoles);
public Roles[] EditRoles { get; }
public Roles[] ViewRoles { get; }
}
public class ViewEditRolesHandler : AuthorizationHandler<ViewEditRolesRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewEditRolesRequirement requirement)
{
if (context.User != null)
{
var canView = requirement.ViewRoles.Any(r => context.User.IsInRole(r.ToString()));
var canEdit = requirement.EditRoles.Any(r => context.User.IsInRole(r.ToString()));
if (context. // Wait, why can't I get to the bloody HttpRequest??
}
return Task.CompletedTask;
}
}
... but I got as far as if (context. before I realized that I didn't have access to the request object.
Is my only choice to override the OnActionExecuting method in the controller and do my authorization there? I assume that's frowned upon, at the very least?
You can't access the Request in a custom AuthorizationHandler, so I can't check the HttpMethod...
Actually, we can access the Request in an AuthorizationHandler. We do that by casting the context.Resource with the as keyword. Here is an example:
services.AddAuthorization(config =>
{
config.AddPolicy("View", p => p.RequireAssertion(context =>
{
var filterContext = context.Resource as AuthorizationFilterContext;
var httpMethod = filterContext.HttpContext.Request.Method;
// add conditional authorization here
return true;
}));
config.AddPolicy("Edit", p => p.RequireAssertion(context =>
{
var filterContext = context.Resource as AuthorizationFilterContext;
var httpMethod = filterContext.HttpContext.Request.Method;
// add conditional authorization here
return true;
}));
});
You can't have more than one AuthorizeAttribute....
Actually, we can have more than one AuthorizeAttribute. Note from the docs that the attribute has AllowMultiple=true. That allows us to "stack" them. Here is an example:
[Authorize(Policy="View")]
[Authorize(Policy="Edit")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
...
}
You can have an IHttpContextAccessor injected into your handler and use it in HandleRequirementAsync:
public class ViewEditRolesHandler : AuthorizationHandler<ViewEditRolesRequirement>
{
private readonly IHttpContextAccessor _contextAccessor;
public ViewEditRolesHandler(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewEditRolesRequirement requirement)
{
if (context.User != null)
{
var canView = requirement.ViewRoles.Any(r => context.User.IsInRole(r.ToString()));
var canEdit = requirement.EditRoles.Any(r => context.User.IsInRole(r.ToString()));
if (_contextAccessor.HttpContext.Request. // Now you have it!
}
return Task.CompletedTask;
}
}
Set up MVC with the extension method
services.AddMvc()
Then in a controller, and this may apply to GET also, create a method for the POST action with a parameter supplied in the body, e.g.
[HttpPost("save")]
public Entity Save([FromBody]Entity someEntity)
When the action is called the MVC pipeline will call the ParameterBinder which in turn calls DefaultObjectValidator. I don't want the validation (its slow for one thing, but more importantly is looping on complex cyclical graphs), but it seems the only way to turn off validation in the pipeline is something like this:
public class NonValidatingValidator : IObjectModelValidator
{
public void Validate(ActionContext actionContext, ValidationStateDictionary validationState, string prefix, object model)
{
}
}
and in the StartUp/ConfigureServices:
var validator = services.FirstOrDefault(s => s.ServiceType == typeof(IObjectModelValidator));
if (validator != null)
{
services.Remove(validator);
services.Add(new ServiceDescriptor(typeof(IObjectModelValidator), _ => new NonValidatingValidator(), ServiceLifetime.Singleton));
}
which seems like a sledgehammer. I've looked around and can't find an alternative, also tried to remove the DataAnnotationModelValidator without success, so would like to know if there's a better/correct way to turn off validation?
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
should disable automatic model state validation.
You should consider to use the ValidateNeverAttribute, which is nearly undocumented and well hidden by Microsoft.
[ValidateNever]
public class Entity
{
....
}
This gives you fine grained control over which entities to validate and which not.
As of aspnet core 3.1, this is how you disable model validation as seen in docs:
First create this NullValidator class:
public class NullObjectModelValidator : IObjectModelValidator
{
public void Validate(ActionContext actionContext,
ValidationStateDictionary validationState, string prefix, object model)
{
}
}
Then use it in place of the real model validator:
services.AddSingleton<IObjectModelValidator, NullObjectModelValidator>();
Note that this only disable Model validation, you'll still get model binding errors.
The .AddMvc() extension method has an overload where you can configure a lot of things. One of these things is the list of ModelValidatorProviders.
If you clear this list, e.g.:
services.AddMvc(options => options.ModelValidatorProviders.Clear());
validation should not take place any longer.
Use this extension method:
public static IServiceCollection DisableDefaultModelValidation(this IServiceCollection services)
{
ServiceDescriptor serviceDescriptor = services.FirstOrDefault<ServiceDescriptor>((Func<ServiceDescriptor, bool>) (s => s.ServiceType == typeof (IObjectModelValidator)));
if (serviceDescriptor != null)
{
services.Remove(serviceDescriptor);
services.Add(new ServiceDescriptor(typeof (IObjectModelValidator), (Func<IServiceProvider, object>) (_ => (object) new EmptyModelValidator()), ServiceLifetime.Singleton));
}
return services;
}
public class EmptyModelValidator : IObjectModelValidator
{
public void Validate(ActionContext actionContext, ValidationStateDictionary validationState, string prefix, object model)
{
}
}
Ussage:
public void ConfigureServices(IServiceCollection services)
{
services.DisableDefaultModelValidation();
}
Create empty model validator class.
public class EmptyModelValidator : IObjectModelValidator {
public void Validate(
ActionContext actionContext,
ValidationStateDictionary validationState,
string prefix,
object model) {
}
}
Replace DefaultModelValidator with EmptyModelValidator in configure services method.
services.Replace(
new ServiceDescriptor(typeof(IObjectModelValidator),
typeof(EmptyModelValidator),
ServiceLifetime.Singleton)
);
EmptyModelValidator not validates model so ModelState.IsValid always return false.
To turn off validation for everything inheriting a class:
var mvc = services.AddMvc(options =>
{
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(VMClass)));
}); // to avoid validation of the complete world by following VMClass refs
Referencing this CodePlex unity article I was able to get filter attribute working with a WebAPI controller as follows:
[MyFilterAttribute]
public class TestController : ApiController
{}
However, if I want to apply my filter attribute across all actions with a GlobalConfiguration it gets stripped of the injected dependency:
public class MyFilterAttribute : ActionFilterAttribute
{
[Dependency]
public MyDependency { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (this.MyDependency == null) //ALWAYS NULL ON GLOBAL CONFIGURATIONS
throw new Exception();
}
}
public static class UnityWebApiActivator
{
public static void Start()
{
var resolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
GlobalConfiguration.Configuration.DependencyResolver = resolver;
GlobalConfiguration.Configuration.Filters.Add(new MyFilterAttribute());
RegisterFilterProviders();
}
private static void RegisterFilterProviders()
{
var providers =
GlobalConfiguration.Configuration.Services.GetFilterProviders().ToList();
GlobalConfiguration.Configuration.Services.Add(
typeof(System.Web.Http.Filters.IFilterProvider),
new UnityActionFilterProvider(UnityConfig.GetConfiguredContainer()));
var defaultprovider = providers.First(p => p is ActionDescriptorFilterProvider);
GlobalConfiguration.Configuration.Services.Remove(
typeof(System.Web.Http.Filters.IFilterProvider),
defaultprovider);
}
}
Is there a better place to add the Global Configuration?
The problem is occurring because you are adding a newed MyFilterAttribute to the filters collection (i.e.: GlobalConfiguration.Configuration.Filters.Add(**new MyFilterAttribute()**)) as opposed to an instance resolved through Unity. Since Unity does not participate in creation of the instance, it has no trigger for injecting the dependency. This should be addressable by simply resolving the instance through Unity. e.g.:
GlobalConfiguration.Configuration.Filters.Add((MyFilterAttribute)resolver.GetService(typeof(MyFilterAttribute()));