I'm trying to hook Fluent Validation to my MVC WEB Api project, and it doesn't wanna work.
When I use MyController : Controller -> works fine (ModelState.IsValid returns False)
but when I use MyController :ApiController ... nothing.
Does anyone have experience on how to hook those up ?
latest version of Fluent Validation (5.0.0.1) supports web api
Just install it from Nuget and register it in Global.asax like so:
using FluentValidation.Mvc.WebApi;
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
FluentValidationModelValidatorProvider.Configure();
}
}
The answer is in this pull request.
Basically You need to implement custom ModelValidation Provider.
And a couple more things to note:
Web API don't work with modelValidator from System.Web.Mvc namespace, only with the ones from System.Web.Http as noted here:
Server side validation with custom DataAnnotationsModelValidatorProvider
You don't add it like this:
ModelValidatorProviders.Providers.Add(new WebApiFluentValidationModelValidatorProvider());`
BUT like this:
GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider), new WebApiFluentValidationModelValidatorProvider());`
I have found another simple solution for using FluentValidation in Web API, but it lacks integration with ModelState and Metadata. However, when building an API that doesn't need to return the entire ModelState to the client (as is needed in MVC to rebuild the page), I have found the trade-off for simplicity to be worthwhile. Whenever an API input is invalid, I return a 400 Bad Request status code with a list of property IDs and error messages. To do this, I use a simple ActionFilterAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateInputsAttribute : ActionFilterAttribute
{
private static readonly IValidatorFactory ValidatorFactory = new AttributedValidatorFactory();
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
var errors = new Dictionary<string, string>();
foreach (KeyValuePair<string, object> arg in actionContext.ActionArguments.Where(a => a.Value != null))
{
var argType = arg.Value.GetType();
IValidator validator = ValidatorFactory.GetValidator(argType);
if (validator != null)
{
var validationResult = validator.Validate(arg.Value);
foreach (ValidationFailure error in validationResult.Errors)
{
errors[error.PropertyName] = error.ErrorMessage;
}
}
}
if (errors.Any())
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
}
}
}
This attribute can be added as a global filter, to individual controllers/actions, or to a base class.
This code can certainly be improved, but it has served me well so far so I wanted to make it available to others. Here are some of its shortcomings:
Null inputs are not validated. I thought that this would be more of a problem, but in practice it simply doesn't happen much (if at all) in our app. My controllers throw ArgumentNullExceptions for null inputs which would return a 500 to the client informing the client that the input cannot be null.
I can't use ModelState in my controllers. But, after validating the required inputs are non-null, I already know that the ModelState is valid so this may actually serve to simplify code. But it's important for devs to know not to use it.
Right now this implementation is hard coded for the AttributedValidatorFactory. This should be abstracted, but it's been pretty low on my priority list so far.
As I was looking to solve this I wanted to make it so that the same validator instance could be used for MVC and Web API. I was able to accomplish this by making two factories and using them together.
MVC Factory:
public class MVCValidationFactory : ValidatorFactoryBase
{
private readonly IKernel _kernel;
public MVCValidationFactory(IKernel kernel)
{
_kernel = kernel;
}
public override IValidator CreateInstance(Type validatorType)
{
var returnType = _kernel.TryGet(validatorType);
return returnType as IValidator;
}
}
API Factory:
public class WebAPIValidationFactory : ModelValidatorProvider
{
private readonly MVCValidationFactory _mvcValidationFactory;
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public WebAPIValidationFactory(MVCValidationFactory mvcValidationFactory)
{
_mvcValidationFactory = mvcValidationFactory;
}
public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<ModelValidatorProvider> validatorProviders)
{
try
{
var type = GetType(metadata);
if (type != null)
{
var fluentValidator =
_mvcValidationFactory.CreateInstance(typeof(FluentValidation.IValidator<>).MakeGenericType(type));
if (fluentValidator != null)
{
yield return new FluentValidationModelValidator(validatorProviders, fluentValidator);
}
}
}
catch (Exception ex)
{
Log.Error(ex);
}
return new List<ModelValidator>();
}
private static Type GetType(ModelMetadata metadata)
{
return metadata.ContainerType != null ? metadata.ContainerType.UnderlyingSystemType : null;
}
The trick then was figuring out how to run the validation for both MVC and Web API. I ended up creating a wrapper for the IValidator<> that worked with the ModelValidator signature.
public class FluentValidationModelValidator : ModelValidator
{
public IValidator innerValidator { get; private set; }
public FluentValidationModelValidator(
IEnumerable<ModelValidatorProvider> validatorProviders, IValidator validator)
: base(validatorProviders)
{
innerValidator = validator;
}
public override IEnumerable<ModelValidationResult> Validate(ModelMetadata metadata, object container)
{
if (InnerValidator != null && container != null)
{
var result = innerValidator.Validate(container);
return GetResults(result);
}
return new List<ModelValidationResult>();
}
private static IEnumerable<ModelValidationResult> GetResults(FluentValidation.Results.ValidationResult result)
{
return result.Errors.Select(error =>
new ModelValidationResult
{
MemberName = error.PropertyName,
Message = error.ErrorMessage
}));
}
}
The last part was to wire up the validators in the Global.asax:
MVCValidationFactory mvcValidationFactory = new MVCValidationFactory(KernelProvider.Instance.GetKernel());
GlobalConfiguration.Configuration.Services.Add(
typeof(ModelValidatorProvider),
new WebAPIValidationFactory(mvcValidationFactory));
ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(mvcValidationFactory));
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
Sorry this was a bit long, but hopefully it helps someone out.
In the WebApiConfig add two lines
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// snip...
//Fluent Validation
config.Filters.Add(new ValidateModelStateFilter());
FluentValidationModelValidatorProvider.Configure(config);
}
}
Create a model and a validator as follows -
[Validator(typeof(PersonCreateRequestModelValidator))]
public class PersonCreateRequestModel
{
public Guid PersonId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
public class PersonCreateRequestModelValidator : AbstractValidator
{
//Simple validator that checks for values in Firstname and Lastname
public PersonCreateRequestModelValidator()
{
RuleFor(r => r.Firstname).NotEmpty();
RuleFor(r => r.Lastname).NotEmpty();
}
}
That's about all you need. Just write the controller as you would normally.
public IHttpActionResult Post([FromBody]PersonCreateRequestModel requestModel)
{
//snip..
//return Ok(some new id);
}
If you want a full source code example you can get it here - http://NoDogmaBlog.bryanhogan.net/2016/12/fluent-validation-with-web-api-2/
Latest version of Fluent Validation doesn't support Mvc 4 or Web Api.
Read this.
Related
I have a custom attribute and I want to check via a GlobalFilter, if the methods I'm calling has this marker attribute.
I can't find a way, to get the information that the called method where my request aims to has the Attribute.
I've found already another post, which recommends to use FilterAttributeCache.GetRequestFilterAttributes(request.GetType()) but this and also other methods are retuning just no elements.
Could you please help me in what I'm missing here?
Following the example code:
CustomAttribute:
using ServiceStack;
using ServiceStack.Web;
namespace MyProject.Web
{
public class CustomAttribute : RequestFilterAttribute
{
public string ServiceName;
public string ServiceClass;
public string ServiceMethod;
public JwtAuthAttribute(string serviceName, string serviceClass, string serviceMethod)
{
ServiceName = serviceName;
ServiceClass = serviceClass;
ServiceMethod = serviceMethod;
}
public override void Execute(IRequest req, IResponse res, object requestDto)
{
//Nothing in here
}
}
}
Filter:
using System;
using System.Linq;
using ServiceStack.Support.WebHost;
using ServiceStack.Web;
namespace MyProject.Web
{
public class CustomFilter
{
private readonly ICustomManager _customManager;
public CustomFilter(ICustomManager customManager)
{
_customManager= customManager;
}
public void Execute(IRequest request)
{
var customHeader = request.Headers.GetValues("CustomHeader");
if (customHeader == null)
{
return;
}
var customAttribute = FilterAttributeCache.GetRequestFilterAttributes(request.GetType())
.OfType<CustomAttribute>().FirstOrDefault();
if (customAttribute == null)
{
return;
}
// Do other things here
}
}
}
Registration of GlobalFilter in Global.asax:
public override void Configure(Container container)
{
//other configurations
GlobalRequestFilters.Add((req, res, dto) => req.ResponseContentType = MimeTypes.Json);
GlobalRequestFilters.Add((req, res, dto) =>
{
var customFilter = new CustomFilter(request.TryResolve<ICustomManager>());
customFilter.Execute(req);
});
}
Method with marker CustomAttribute:
namespace MyProject.Web
{
[Authenticate]
[RequiredRole("CustomUser")]
public class CustomService : Service
{
[Custom("Service", "ServiceClass", "ServiceMethod")]
public object Get(CustomRequest request)
{
//Additional code
}
}
}
I'd recommend adding attributes on the Request DTO which is much easier to access since the Request DTO is available everywhere throughout ServiceStack request pipeline, e.g. dto.GetType() in global filters or IRequest.Dto.GetType() everywhere else.
To access ServiceStack actions, you'd need to access the service Type then its actions, e.g:
GlobalRequestFilters.Add((req, res, dto) =>
{
var serviceType = HostContext.AppHost.Metadata.GetServiceTypeByRequest(dto.GetType());
var action = serviceType.GetActions().First(x => x.RequestType == dto.GetType());
var attrs = action.AllAttributes<CustomAttribute>();
});
After the answer from mythz I was able to fix the issue. I've extented the "Execute" method of "CustomFilter" by requestDto, which was provided by the GobalRequestFilter registration.
Unfortunately I was not able to access the Property "RequestType", maybe because I'm using an older version (ServiceStack 5.9.2, .Net Framework 4.7.2), so I had to go over the MethodParameters.
Here is the final code, which gave me the access to the "CustomAttribute"
var serviceTypeDto = HostContext.AppHost.Metadata.GetServiceTypeByRequest(requestDto.GetType());
var methodInfo = serviceTypeDto.GetDeclaredMethods().FirstOrDefault(x =>
x.GetParameters().FirstOrDefault(y => y.ParameterType == requestDto.GetType()) != null && x.IsPublic);
var customAttribute = methodInfo.AllAttributes<CustomAttribute>().FirstOrDefault();
I'm writing a RestFramework and I'm trying to figure out how I can allow the users to create a custom name for a generic controller. I'm registering my generic controllers like so:
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var entityConfig in _entityConfigurations)
{
var entityType = entityConfig.Type;
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType.AsType())
.GetTypeInfo();
//Normally I would expect there to be an overload to configure the controller name
//feature.Controllers.Add(controllerType, entityConfig.ControllerName);
}
}
}
}
How ever I need to figure out a way that I can override the route for the controllers. The only information about this in the documentation shows how to create a controller convention like so:
public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericController<>))
{
return;
}
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name;
}
}
This will not work since it is done at compile time. I need user to be able to override the controller name on Startup, How can I Achieve this?
Based on your comment and code you were pretty much on par with how you would achieve this. Note I have cut down the example quite a bit so I could setup a test.
Say I have a basic generic controller as:
public class GenericController<T> : Controller
where T: class
{
public IActionResult Get()
{
return Content(typeof(T).FullName);
}
}
I now have a typed controller with Get action. Now most of your code was right on the money. So my Feature Provider as (note i have a static array of types):
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var entityConfig in ControllerEntity.EntityTypes)
{
var entityType = entityConfig;
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType)
.GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}
Next the IControllerModelConvention implementation.
public class GenericControllerModelConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (!controller.ControllerType.IsGenericType || controller.ControllerType.GetGenericTypeDefinition() != typeof(GenericController<>))
{
return;
}
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name + "Controller";
controller.RouteValues["Controller"] = entityType.Name;
}
}
And finally the startup is where all the magic happens. Basically we register the IControllerModelConvention into the MVC convention options, and then register the FeatureProvider.
public void ConfigureServices(IServiceCollection services)
{
var mvcBuilder = services.AddMvc();
mvcBuilder.AddMvcOptions(o => o.Conventions.Add(new GenericControllerModelConvention()));
mvcBuilder.ConfigureApplicationPartManager(c =>
{
c.FeatureProviders.Add(new GenericControllerFeatureProvider());
});
}
From my review two things struck me.
I am not sure why you have your GenericControllerNameConvention as an attribute?
You should implicitly set the Controller Route Value to your entity type (not the type + name).
Given two entities (EntityA and EntityB) the result of the controllers is
/Entitya/get/ prints WebApplication11.Infrastructure.EntityA
/Entityb/get/ prints WebApplication11.Infrastructure.EntityB
I've got a pretty basic controller method that returns a list of Customers. I want it to return the List View when a user browses to it, and return JSON to requests that have application/json in the Accept header.
Is that possible in ASP.NET Core MVC 1.0?
I've tried this:
[HttpGet("")]
public async Task<IActionResult> List(int page = 1, int count = 20)
{
var customers = await _customerService.GetCustomers(page, count);
return Ok(customers.Select(c => new { c.Id, c.Name }));
}
But that returns JSON by default, even if it's not in the Accept list. If I hit "/customers" in my browser, I get the JSON output, not my view.
I thought I might need to write an OutputFormatter that handled text/html, but I can't figure out how I can call the View() method from an OutputFormatter, since those methods are on Controller, and I'd need to know the name of the View I wanted to render.
Is there a method or property I can call to check if MVC will be able to find an OutputFormatter to render? Something like the following:
[HttpGet("")]
public async Task<IActionResult> List(int page = 1, int count = 20)
{
var customers = await _customerService.GetCustomers(page, count);
if(Response.WillUseContentNegotiation)
{
return Ok(customers.Select(c => new { c.Id, c.Name }));
}
else
{
return View(customers.Select(c => new { c.Id, c.Name }));
}
}
I think this is a reasonable use case as it would simplify creating APIs that return both HTML and JSON/XML/etc from a single controller. This would allow for progressive enhancement, as well as several other benefits, though it might not work well in cases where the API and Mvc behavior needs to be drastically different.
I have done this with a custom filter, with some caveats below:
public class ViewIfAcceptHtmlAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.HttpContext.Request.Headers["Accept"].ToString().Contains("text/html"))
{
var originalResult = context.Result as ObjectResult;
var controller = context.Controller as Controller;
if(originalResult != null && controller != null)
{
var model = originalResult.Value;
var newResult = controller.View(model);
newResult.StatusCode = originalResult.StatusCode;
context.Result = newResult;
}
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
which can be added to a controller or action:
[ViewIfAcceptHtml]
[Route("/foo/")]
public IActionResult Get(){
return Ok(new Foo());
}
or registered globally in Startup.cs
services.AddMvc(x=>
{
x.Filters.Add(new ViewIfAcceptHtmlAttribute());
});
This works for my use case and accomplishes the goal of supporting text/html and application/json from the same controller. I suspect isn't the "best" approach as it side-steps the custom formatters. Ideally (in my mind), this code would just be another Formatter like Xml and Json, but that outputs Html using the View rendering engine. That interface is a little more involved, though, and this was the simplest thing that works for now.
I haven't tried this, but could you just test for that content type in the request and return accordingly:
var result = customers.Select(c => new { c.Id, c.Name });
if (Request.Headers["Accept"].Contains("application/json"))
return Json(result);
else
return View(result);
I liked Daniel's idea and felt inspired, so here's a convention based approach as well. Because often the ViewModel needs to include a little bit more 'stuff' than just the raw data returned from the API, and it also might need to check different stuff before it does its work, this will allow for that and help in following a ViewModel for every View principal. Using this convention, you can write two controller methods <Action> and <Action>View both of which will map to the same route. The constraint applied will choose <Action>View if "text/html" is in the Accept header.
public class ContentNegotiationConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
if (action.ActionName.ToLower().EndsWith("view"))
{
//Make it match to the action of the same name without 'view', exa: IndexView => Index
action.ActionName = action.ActionName.Substring(0, action.ActionName.Length - 4);
foreach (var selector in action.Selectors)
//Add a constraint which will choose this action over the API action when the content type is apprpriate
selector.ActionConstraints.Add(new TextHtmlContentTypeActionConstraint());
}
}
}
public class TextHtmlContentTypeActionConstraint : ContentTypeActionConstraint
{
public TextHtmlContentTypeActionConstraint() : base("text/html") { }
}
public class ContentTypeActionConstraint : IActionConstraint, IActionConstraintMetadata
{
string _contentType;
public ContentTypeActionConstraint(string contentType)
{
_contentType = contentType;
}
public int Order => -10;
public bool Accept(ActionConstraintContext context) =>
context.RouteContext.HttpContext.Request.Headers["Accept"].ToString().Contains(_contentType);
}
which is added in startup here:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o => { o.Conventions.Add(new ContentNegotiationConvention()); });
}
In you controller, you can write method pairs like:
public class HomeController : Controller
{
public ObjectResult Index()
{
//General checks
return Ok(new IndexDataModel() { Property = "Data" });
}
public ViewResult IndexView()
{
//View specific checks
return View(new IndexViewModel(Index()));
}
}
Where I've created ViewModel classes meant to take the output of API actions, another pattern which connects the API to the View output and reinforces the intent that these two represent the same action:
public class IndexViewModel : ViewModelBase
{
public string ViewOnlyProperty { get; set; }
public string ExposedDataModelProperty { get; set; }
public IndexViewModel(IndexDataModel model) : base(model)
{
ExposedDataModelProperty = model?.Property;
ViewOnlyProperty = ExposedDataModelProperty + " for a View";
}
public IndexViewModel(ObjectResult apiResult) : this(apiResult.Value as IndexDataModel) { }
}
public class ViewModelBase
{
protected ApiModelBase _model;
public ViewModelBase(ApiModelBase model)
{
_model = model;
}
}
public class ApiModelBase { }
public class IndexDataModel : ApiModelBase
{
public string Property { get; internal set; }
}
I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]
I am interested in building a startup routine for my API to check that certain configuration values in the web.config are present. If the routine does not contain values I would like to redirect to a route, log the missing configuration item and display a custom application offline page.
Any assistance in pointing me in the right direction would be appreciated.
Guard Class
public static class Guard
{
public static bool ConfigurationValueExists(string key, [CallerMemberName] string caller = null)
{
if (!string.IsNullOrEmpty(Configuration.GetAppConfig(key, string.Empty))) return true;
ApiLogger.Log($"The configuration value {key} is not present and needs to be defined. Calling method is {caller}.");
return false;
}
}
Configuration Class
public static class Configuration
{
public static T GetAppConfig<T>(string key, T defaultVal = default(T))
{
if (null == ConfigurationManager.AppSettings[key])
{
return defaultVal;
}
return string.IsNullOrEmpty(key)
? defaultVal
: Generic.Turn(ConfigurationManager.AppSettings[key], defaultVal);
}
public static bool ConfigurationsAreInPlace()
{
return AssertMainApplicationConfiguration();
}
private static bool AssertMainApplicationConfiguration()
{
return Guard.ConfigurationValueExists("MyKey1");
}
}
I would like to be able to call ConfigurationsAreInPlace on the startup routine and redirect to my custom offline page.
I decided to create an Index Controller and use the Route Attribute of root to override what happens on the page. I then do the check if configurations are in place and issue a new response as needed.
Code if interested:
public class IndexController : ApiController
{
[AllowAnonymous]
[Route]
public HttpResponseMessage GetIndex()
{
string startUrl = "/help/";
if (!Helpers.Configuration.ConfigurationsAreInPlace())
{
startUrl += "offline";
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Moved);
string fullyQualifiedUrl = Request.RequestUri.GetLeftPart(UriPartial.Authority);
response.Headers.Location = new Uri(fullyQualifiedUrl + startUrl);
return response;
}
}