How to ensure ASP.net Web API controller's parameter is not null? - asp.net-web-api

I created a ASP.net Web API controller like that:
public class UsersController : ApiController
{
//...
public void Put([FromBody]User_API user, long UpdateTicks)
{
user.UpdateTicks = UpdateTicks;
//...
}
}
The "user" parameter will be null if the client does not provide correct arguments. Can I make a global filter to check every parameter like this, and will return a 400 message if any error occurs.

Finally, I got the solution:
public class ModelValidateFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.Any(v => v.Value==null))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
And...
//In Application_Start()
GlobalConfiguration.Configuration.Filters.Add(new ModelValidateFilterAttribute());

Related

Custom action filter (with dependencies) not working in ASP.NET Core 3.1 Web API

I'm having some difficulties getting a custom action filter to work in ASP.NET Core 3.1 Web API. I've followed this SO, as well as the Microsoft docs, but it's not working. I've created a simple filter (note: I need Dependency Injection);
public class LogFilterAttribute : ActionFilterAttribute, IFilterMetadata
{
private readonly ILogger<LogFilterAttribute> _logger;
public LogFilterAttribute(ILogger<LogFilterAttribute> logger)
{
_logger = logger;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_logger.LogWarning("test");
base.OnActionExecuting(actionContext);
}
}
Notes:
ActionFilterAttribute is from System.Web.Http.Filters namespace.
I implemented IFilterMetadata (which is just a marker interface) as this seems to be required by ServiceFilter and TypeFilter.
I'm registering this in ConfigureServices of Startup.cs as follows:
services.AddScoped<LogFilterAttribute>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
and then applying this in my Web API controller as follows:
[ApiVersion("1.0")]
[ApiController]
[Route("v{version:apiVersion}/resources/{id}")]
public class ResourceController : ControllerBase
{
private readonly ILogger<ResourceController> _logger;
public ResourceController(ILogger<ResourceController> logger)
{
_logger = logger;
}
[HttpGet]
[ServiceFilter(typeof(LogFilterAttribute))]
public async Task<IActionResult> Get([FromRoute(Name = "id")] string id)
{
_logger.LogInformation($"{typeof(ResourceController)}.{nameof(Get)}");
return Ok();
}
}
I've tried with both ServiceFilter and TypeFilter, but to no avail - it just skips the break-point in filter and goes straight to my route logic. What am I doing wrong?
Try implementing IActionFilter in place of ActionFilterAttribute
In the end I solved the issue by implementing IAsyncActionFilter and inheriting from Attribute as follows:
public class LogFilterActionFilterAttribute : Attribute, IAsyncActionFilter
{
public LogFilterActionFilterAttribute(...)
{
...
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
...
}
}
I also override TypeFilterAttribute as follows:
public class LogFilterAttribute : TypeFilterAttribute
{
public LogFilterAttribute (...) : base(typeof(LogFilterActionFilterAttribute))
{
Arguments = new object[] { ... };
}
}
So that I can decorate on controllers/routes as follows:
[ApiVersion("1.0")]
[ApiController]
[Route("v{version:apiVersion}/resources/{id}")]
public class ResourceController : ControllerBase
{
private readonly ILogger<ResourceController> _logger;
public ResourceController(ILogger<ResourceController> logger)
{
_logger = logger;
}
[HttpGet]
[LogFilter(...)]
public async Task<IActionResult> Get([FromRoute(Name = "id")] string id)
{
_logger.LogInformation($"{typeof(ResourceController)}.{nameof(Get)}");
return Ok();
}
}
In StartUp.cs, Add the filter in MVC pipeline.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(LogFilterAttribute));
});
}
You need to use this on the controller/method as you're using a type filter, isn't the logger already scoped within the configuration? if so you need a type filter
[TypeFilter(typeof(LogFilterAttribute))]
For my use, I don't need to add IFilterMetadata

Conditional validation based on request route with asp.net core 2.2 and FluentValidation

So Basically i wrote a validator for my class with FluentValidation and also a filter to do the validation task for me in my webAPI project, so far it's OK but assume that my User class has firstname,lastname,email,password properties
and i have two routes (one for register and the other one for login)
and as you might have noticed required properties are different on these route.
Thus,should I really need to write individual validation for each and every action i have?because this makes a lot of code code duplication and it's hard to change.is there any way to just add required condition based on the request coming with single validation class?
Any suggestion???
A better practice would be to use a factory pattern for your validations and use a an action filter to short circuit bad requests. You could validate any action argument(Headers, Request Bodies, etc..) with something like this.
public class TestValidationAttribute : Attribute, IActionFilter
{
private string _requestModelName;
public TestValidationAttribute(string requestModelName)
{
_requestModelName = requestModelName;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// using Microsoft.Extensions.DependencyInjection;
var services = context.HttpContext.RequestServices;
var accessor = services.GetService<IHttpContextAccessor>();
var factory = services.GetService<ITestValidatorFactory>();
var tokens = accessor.HttpContext.GetRouteData().DataTokens;
if (!tokens.TryGetValue("RouteName", out var routeNameObj))
{
throw new Exception($"Action doesn't have a named route.");
}
var routeName = routeNameObj.ToString();
var validator = factory.Create(routeName);
if (!context.ActionArguments.TryGetValue(_requestModelName, out var model))
{
throw new Exception($"Action doesn't have argument named {_requestModelName}.");
}
TestModel test;
try
{
test = (TestModel) model;
}
catch (InvalidCastException)
{
throw new Exception($"Action argument can't be casted to {nameof(TestModel)}.");
}
var validation = validator.Validate(test);
if (!validation.Successful)
{
context.Result = new BadRequestObjectResult(validation.ResponseModel);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
public class TestController : Controller
{
[HttpPost]
[Route("Test/{id}", Name = "TestGet")]
[TestValidation("model")]
public IActionResult Test(TestModel model)
{
return Ok();
}
}
public class ValidationResult
{
public bool Successful { get; }
public ResponseModel ResponseModel { get; }
}
public class TestModel
{
}
public interface ITestValidator
{
ValidationResult Validate(TestModel model);
}
public interface ITestValidatorFactory
{
ITestValidator Create(string routeName);
}

ASP.NET Core WebAPI 404 error

I create a Web Api in asp.net core this the content of Api:
[Route("api/[controller]")]
public class BlogController : Controller
{
public IContext _context { get; set; }
public BlogController(IContext ctx)
{
_context = ctx;
}
[HttpGet]
[Route("api/Blog/GetAllBlog")]
public List<Blog> GetAllBlog()
{
return _context.Blogs.ToList();
}
}
as i know in ASp.net Core (WebApi Template) we don't need any configuration like registration Route, which we need in Asp.net Mvc 5.3 and older.
So when i try to call the GetAllBlog by browser or Postman, by this url http://localhost:14742/api/Blog/GetAllBlog , it gets me 404 error, what is problem?
You have already included the api/[controller] route at the top of the controller class so you don't need to include it again while defining route for accessing method.
In essence, change the Route to api/Blog/GetAllBlog to GetAllBlog. Your code should look like this:
[Route("api/[controller]")]
public class BlogController : Controller
{
public IContext _context { get; set; }
public BlogController(IContext ctx)
{
_context = ctx;
}
[HttpGet]
[Route("GetAllBlog")]
public List<Blog> GetAllBlog()
{
return _context.Blogs.ToList();
}
[HttpGet]
[Route("GetOldBlogs")]
public List<Blog> GetOldBlogs()
{
return _context.Blogs.Where(x => x.CreationDate <= DateTime.Now.AddYears(-2)).ToList();
}
}
You also need to have different route names for methods.
Hope this helps.

How multiple Get function could be there in a web api controller

just see the scenario
public class CustomerController : ApiController
{
public IEnumerable<Customer> GetCustomersByID(int id)
{
}
public IEnumerable<Customer> GetCustomersByName(string name)
{
}
public IEnumerable<Customer> GetCustomersByEmail(string strEmail)
{
}
}
now tell me what i need to do as a result end user can call three get action by their name. how to handle this situation. thanks
You can set route for each method. Such as:
[Route("GetById/{id}")]
public IEnumerable<Customer> GetCustomersByID(int id)
{
}
You can call it getbyid/3. More details web api routing
Also there is a question for this issue.

MVC controller injection with custom base controller not working

I am using StructureMap for DI and am having an issue with my MVC3 custom base controller not being instantiated correctly. Instead of being passed an instance of IAuctionCmsServices, I am getting null.
My controllers:
public class BaseController : Controller
{
public IAuctionCmsServices AuctionCmsServices;
public BaseController()
: this(null) <--- is this the problem?
{
}
public BaseController(IAuctionCmsServices auctionCmsServices)
{
this.AuctionCmsServices = auctionCmsServices;
}
}
public class HomeController : BaseController
{
public ActionResult Index()
{
return View);
}
}
StructureMap code:
public class StructureMapContainer : IDependencyResolver
{
static IContainer _container;
public StructureMapContainer(IContainer container)
{
_container = container;
}
public object GetService(Type serviceType)
{
if (serviceType.IsAbstract || serviceType.IsInterface)
{
return _container.TryGetInstance(serviceType);
}
else
{
System.Diagnostics.Debug.WriteLine(_container.WhatDoIHave());
return _container.GetInstance(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances<object>().Where(s => s.GetType() == serviceType);
}
}
public class ApplicationRegistry : Registry
{
public ApplicationRegistry()
{
For<IAuctionCmsServices>().HybridHttpOrThreadLocalScoped().Use<AuctionCmsServices>();
}
}
In global.asax.cs:
DependencyResolver.SetResolver(new StructureMapContainer(container));
When BaseController's constructor is invoked, the IAuctionCmsServices parameter is null. If I remove the this(null) from the constructor, I still get null.
Perhaps my BaseController's paramterless constructor is not written correctly? If I resolve for IAuctionCmsServices manually, it works. This means that IAuctionCmsServices is registered correctly but not being injected.
Did you leave out the constructor HomeController from the sample or do you not have one?
Im not really familiar with StructureMap, so unless it does some wizardry with the IL, how is the property supposed to be injected into HomeController if it does not have a constructor that accepts it?
IE
public HomeController(IAuctionCmsServices auctionCmsServices)
: base(auctionCmsServices)
{}
Apologies if Im being dense or missing something.

Resources