When using ASP.NET Web Api 2 I always need to include the same code:
public IHttpActionResult SomeMethod1(Model1 model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//...
}
public IHttpActionResult SomeMethod2(Model2 model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//...
}
I would like to move validation to the base controller that will be executed on each request. But there are many methods to override and I don't know, which one should I use and how.
public class BaseController : ApiController
{
public void override SomeMethod(...)
{
if (!ModelState.IsValid)
{
// ???
}
}
}
Is there any example for validation in a base class for ASP.NET Web Api?
Example from asp.net
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
and add this attribute to your methods
[ValidateModel]
public HttpResponseMessage SomeMethod1(Model1 model)
Related
i have an problem with call to api, i get ambiguousMatchException: The request matched multiple endpoints
or page not found.
this happen after i add testApi to my controller, before all works good.
why it's happen and how to fix it? i want to add more function to this controller.
thank's!!!!!!
my code:
namespace AutomationTool.Api
{
[Route("api/[controller]")]
[ApiController]
public class TestCasesController : ControllerBase
{
private readonly ApplicationDbContext _context;
public TestCasesController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet("{id:int}")] -- > here the problem
public int testApi(int id)
{
return 1;
}
// GET: api/TestCases
[HttpGet]
public async Task<ActionResult<IEnumerable<TestCase>>> GetTestCases()
{
return await _context.TestCases.ToListAsync();
}
// GET: api/TestCases/5
[HttpGet("{id}")]
public async Task<ActionResult<TestCase>> GetTestCase(int id)
{
var testCase = await _context.TestCases.FindAsync(id);
if (testCase == null)
{
return NotFound();
}
return testCase;
}
}
}
I'm a newbine in ASP.NET Core, I see in the User property (in ClaimsPrincipal class) in my controller, it has User.IsInRole method, so how can I override it to call my service dependency and register in my application (I don't want to use extension method).
You can use ClaimsTransformation:
public class Startup
{
public void ConfigureServices(ServiceCollection services)
{
// ...
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
}
}
public class CustomClaimsPrincipal : ClaimsPrincipal
{
public CustomClaimsPrincipal(IPrincipal principal): base(principal)
{}
public override bool IsInRole(string role)
{
// ...
return base.IsInRole(role);
}
}
public class ClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var customPrincipal = new CustomClaimsPrincipal(principal) as ClaimsPrincipal;
return Task.FromResult(customPrincipal);
}
}
Controller method:
[Authorize(Roles = "Administrator")]
public IActionResult Get()
{
// ...
}
Role checking by Authorize attribute will use your overrided IsInRole method
For User.IsInRole, it is ClaimsPrincipal which is not registered as service, so, you could not replace ClaimsPrincipal, and you could not override IsInRole.
For a workaround, if you would not use extension method, you could try to implement your own ClaimsPrincipal and Controller.
CustomClaimsPrincipal which is inherited from ClaimsPrincipal
public class CustomClaimsPrincipal: ClaimsPrincipal
{
public CustomClaimsPrincipal(IPrincipal principal):base(principal)
{
}
public override bool IsInRole(string role)
{
return base.IsInRole(role);
}
}
ControllerBase to change ClaimsPrincipal User to CustomClaimsPrincipal User
public class ControllerBase: Controller
{
public new CustomClaimsPrincipal User => new CustomClaimsPrincipal(base.User);
}
Change the Controller from inheriting ControllerBase.
public class HomeController : ControllerBase
{
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
var result = User.IsInRole("Admin");
return View();
}
Change the logic in public override bool IsInRole(string role) based on your requirement
I writted this code (CustomHandle) for application log. But, i don't want to run this code on some actions.
CustomHandle.cs:
public class CustomHandle: ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
string FormVeri = "";
string QueryVeri = "";
foreach (var fName in filterContext.HttpContext.Request.Form)
{
FormVeri += fName + "= " + filterContext.HttpContext.Request.Form[fName.ToString()].ToString() + "& ";
}
foreach (var fQuery in filterContext.HttpContext.Request.QueryString)
{
QueryVeri += fQuery + "= " + filterContext.HttpContext.Request.QueryString[fQuery.ToString()] + "& ";
}
base.OnResultExecuted(filterContext);
}
}
FilterConfig.cs:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomHandle());
}
}
HomeController.cs:
public ActionResult Index()
{
return View();
}
public ActionResult Login()
{
return View();
}
CustomHandle works on Index and Login. But, CustomHandle is i don't want run on Login ActionResult.
Thanks,
Best Regards.
In MVC 5... instead of adding the action filter in FilterConfig.cs
add it to each Controller (or a base controller) - all actions will be affected.
use [OverrideActionFilter] to remove that filter for a specific action.
Example
[CustomHandle]
public class AnyController : Controller
{
public ActionResult Index() // has [CustomHandle] attribute
{
}
[OverrideActionFilter]
public ActionResult Login() // ignores the [CustomHandle] attribute
{
}
}
When a filter is injected into a controller class, all its actions are also injected. If you would like to apply the filter only for a set of actions, you would have to inject [CustomActionFilter] to each one of them:
[CustomHandle]
public ActionResult Index()
{
...
}
public ActionResult Login()
{
...
}
I'm struggling with ASP.NET MVC 6 (beta 4 release) trying to inject a service within a controller filter attribute of type AuthorizationFilterAttribute.
This is the service (it has another service injected)
public class UsersTableRepository
{
private readonly NeurosgarContext _dbContext;
public UsersTableRepository(NeurosgarContext DbContext)
{
_dbContext = DbContext;
}
public ICollection<User> AllUsers
{
get
{
return _dbContext.Users.ToList();
}
}
//other stuff...
}
This is the ConfigureServices method in Startup class for services enabling
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<NeurosgarContext>(a => NeurosgarContextFactory.GetContext());
services.AddSingleton<UifTableRepository<Nazione>>();
services.AddSingleton<UsersTableRepository>();
}
A simple "dummy" controller with two filters defined on it. You can notice that I already done DI inside this controller by decorating the property with [FromServices]and it works.
[Route("[controller]")]
[BasicAuthenticationFilter(Order = 0)]
[BasicAuthorizationFilter("Admin", Order = 1)]
public class DummyController : Controller
{
[FromServices]
public UsersTableRepository UsersRepository { get; set; }
// GET: /<controller>/
[Route("[action]")]
public IActionResult Index()
{
return View();
}
}
Doing the same DI within BasicAuthenticationFilterdoes not work and at runtime UserRepository property is a null reference.
public class BasicAuthenticationFilterAttribute : AuthorizationFilterAttribute
{
[FromServices]
public UsersTableRepository UsersRepository { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!Authenticate(filterContext.HttpContext))
{
// 401 Response
var result = new HttpUnauthorizedResult();
// Add the header for Basic authentication require
filterContext.HttpContext.Response.Headers.Append("WWW-Authenticate", "Basic");
filterContext.Result = result;
//if (!HasAllowAnonymous(context))
//{
// base.Fail(context);
//}
}
}
// ...
}
Any idea about how solve this?
Refrain from injecting dependencies into your attributes as explained here. Make your attributes passive, or make your attribute a humble object as described here.
var dependencyScope = context.HttpContext.RequestServices;
var usersRepository = dependencyScope.GetService(typeof(UsersTableRepository)) as UsersTableRepository;
// usersRepository is now ready to be used
So your BasicAuthenticationFilter will look like this:
public class BasicAuthenticationFilterAttribute : AuthorizationFilterAttribute
{
public UsersTableRepository UsersRepository { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
var dependencyScope = context.HttpContext.RequestServices;
UsersRepository = dependencyScope.GetService(typeof(UsersTableRepository)) as UsersTableRepository;
if (!Authenticate(filterContext.HttpContext))
{
// 401 Response
var result = new HttpUnauthorizedResult();
// Add the header for Basic authentication require
filterContext.HttpContext.Response.Headers.Append("WWW-Authenticate", "Basic");
filterContext.Result = result;
//if (!HasAllowAnonymous(context))
//{
// base.Fail(context);
//}
}
}
// ...
}
I have one controller that takes a username and pass and checks against a database. IF the user is authenticated, I want to call an overloaded action on another controller.
My end goal is to authenticate a user against an old table from a MySQL db (I have this part working). Once the user is authenticated, I would like to be able to "automagically" forward the person to the built in MVC registration page but I would like to populate some fields in the view using data obtained from the first controller (the old databse info).
When I try something like what I have below I get an error about the Register() methods being ambiguous. I've also tried using the [ActionName("Register2")] attribute but then the error returned says it cant find a method named Register2.
public class MigrateAccountController : Controller
{
OldUserRepository oldDb = new OldUserRepository();
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
return RedirectToAction("Register", "Account", u);
return View(u);
}
}
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
First thing you cannot have the same action name on the same controller that is accessible on the same verb. You need to either change the action name or use a different HTTP verb:
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
[HttpPost]
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
and in order to pass data between actions, well, if you are using GET, you could pass them as query string parameters when redirecting.
Or IMHO a better way would be not to redirect in this case but simply return the corresponding view by passing it the proper view model:
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
{
return View("~/Account/Register.aspx", u);
}
return View(u);
}
You can use the TempData values in this case.