Using MVC 4 & WebAPI, how do I redirect to an alternate service endpoint from within a custom filter? - asp.net-mvc-4

Thanks for looking.
This is a trivial task when using a normal (not WebAPI) action filter as I can just alter the filterContext.Result property like so:
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "controller", "Home" }, {"action", "Index" } });
Unfortunately, I have to use HttpActionContext for WebAPI, so I can not access filterContext.Result.
So what should I do in place of that? I have the filter set up and it does execute at the appropriate time, I just don't know how to make it prevent execution of the requested service endpoint and instead point to a different one.
Here is my controller:
[VerifyToken]
public class ProductController : ApiController
{
#region Public
public List<DAL.Product.CategoryModel> ProductCategories(GenericTokenModel req)
{
return HelperMethods.Cacheable(BLL.Product.GetProductCategories, "AllCategories");
}
public string Error() //This is the endpoint I would like to reach from the filter!
{
return "Not Authorized";
}
#endregion Public
#region Models
public class GenericTokenModel
{
public string Token { get; set; }
}
#endregion Models
}
Here is my filter:
using System.Web.Http.Controllers;
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
namespace Web.Filters
{
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
//How do I redirect from here??
}
base.OnActionExecuting(filterContext);
}
}
}
Any help is appreciated.

The answer in my case was simply to change the Response property of the filterContext rather than to redirect to a different endpoint. This achieved the desired result.
Here is the revised filter:
public class VerifyTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
dynamic test = filterContext.ActionArguments["req"];
if (test.Token != "foo")
{
filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
base.OnActionExecuting(filterContext);
}
}

Related

Action filter does not override controller action?

I have implemented an IAsyncAuthorizationFilter/IActionFilter filter and implemented TypeFilterAttribute for the filter. When I add the attribute to both the controller and action, the action filter does not appear to override the controller level filter.
public class MyAuthorizeAttribute : TypeFilterAttribute
{
public MyAuthorizeAttribute (bool redirectOnFailure = true)
: base(typeof(MyFilter))
{
Arguments = new object[]
{
redirectOnFailure
};
}
}
public class MyFilter: IAsyncAuthorizationFilter, IActionFilter
{
public bool RedirectOnFailure { get; set; }
public MyFilter(bool redirectOnFailure)
{
RedirectOnFailure = redirectOnFailure;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.Controller is Controller controller)
{
// Do some work
if (true)
{
if (!RedirectOnFailure)
{
context.Result = new JsonResult("Your session has expired.");
}
else
{
context.Result = new RedirectResult("LoginUrl");
}
return;
}
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do nothing
}
public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// Do work
}
}
The redirectOnFailure will be true for the Index action even though the filter specified false. In ASP.NET MVC, the action filter would override the controller filter. You could have a default for all actions but override specific actions with different properties/parameters. Can you not do this in Core?
[MyAuthorize]
public class HomeController : Controller
{
[MyAuthorize(redirectOnFailure: false)]
public IActionResult Index()
{
// Do work
}
}
As per the Microsoft website, filters do not override each other. They simply run one after the other in the order described in the cited document.
Just because the same attribute is put in both the controller and the action doesn't mean that ASP.net will say "ah, you probably want to override the class-level attribute". That's just not how it works.
If you want override logic, you need to write override logic.
Here's a sample made for .Net 6. The magic is done by the FindEffectivePolicy() method. This sample shows how to compare the current object against the effective one and only run the logic if the comparison matches.
public class MyFilter : IAsyncAuthorizationFilter
{
#region Properties
public string Name { get; }
#endregion
#region Constructors
public MyFilter(string name)
{
Name = name;
}
#endregion
#region IAsyncAuthorizationFilter
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var effectiveAtt = context.FindEffectivePolicy<MyFilter>();
System.Diagnostics.Debug.Print($"Effective filter's name: {effectiveAtt?.Name}");
System.Diagnostics.Debug.Print($"Am I the effective attribute? {this == effectiveAtt}");
if (this == effectiveAtt)
{
// Do stuff since this is the effective attribute (policy).
}
else
{
// ELSE part probably not needed. We just want the IF to make sure the code runs only once.
}
return Task.CompletedTask;
}
#endregion
}

asp.net core 2.1 odata use different name of entity in the route

I have a long name of of entity in my code EmployeTraining which used as entity in OData and with same name for the controller.
Startup.cs
app.UseMvc(routeBuilder=>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter().MaxTop(null);
routeBuilder.MapODataServiceRoute("EmployeTraining", "odata/v1", EdmModelBuilder.GetEdmModelEmploye());
});
EdmModelBuilder.cs
public static IEdmModel GetEdmModelEmployes()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EmployeTraining>("EmployeTraining");
return builder.GetEdmModel();
}
EmployeTrainingControllers.cs
public class EmployeTrainingController : ODataController
{
internal IEmployeService ServiceEmploye { get; set; }
public EmployesController(IEmployeService serviceEmploye)
{
ServiceEmploye = serviceEmploye;
}
//// GET api/employes
[HttpGet]
[MyCustomQueryable()]
public IQueryable<EmployeTraining> Get()
{
return ServiceEmploye.GetListeEmployes();
}
}
To call my service it works only through this URL: https://{server}/odata/v1/rh/employetraining
but I need to use this https://{server}/odata/v1/rh/employe-training
any help please.
For such scenario,change like below:
1.Change the entityset name:
public static class EdmModelBuilder
{
public static IEdmModel GetEdmModelEmployes()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EmployeTraining>("employe-training");
return builder.GetEdmModel();
}
}
2.Add the attribute:
public class EmployeTrainingController : ODataController
{
[HttpGet]
[ODataRoute("employe-training")]
//[MyCustomQueryable()]
public IQueryable<EmployeTraining> Get()
{
return ServiceEmploye.GetListeEmployes();
}
}
3.Startup.cs:
app.UseMvc(routeBuilder=>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter().MaxTop(null);
routeBuilder.MapODataServiceRoute("EmployeTraining", "odata/v1/rh", EdmModelBuilder.GetEdmModelEmploye());
});
Request the url:https://{server}/odata/v1/rh/employe-training
The Reason why is working using https://{server}/odata/v1/rh/employetraining is because is the Get method of the EmployeTrainingController Controller.
You should be able to change that behaibour if you modify the [HttpGet] on the Get method to [HttpGet("employe-training")]

Unable to get custom attributes in asp.net action filter

I have created a custom attribute and I am trying to retrieve the value of this custom attribute in asp.net action filter but it seems to be unavailable. What am I doing wrong?
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public sealed class MyCustomAttribute : Attribute
{
MyCustomAttribute(string param)
{
}
}
public class MyCustomActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
throw new NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// unable to find my custom attribute here under context.Filters or anywhere else.
}
}
[HttpGet]
[MyCustomAttribute ("test123")]
public async Task<Details> GetDetails(
{
}
What you want to achieve is a little more complicated if you want to do it yourself (ie. reflecting attribute value from method of Controller).
I would recommend using built-in attribute filters from ASP.NET Core (more in ASP.NET Core documentation), in your example:
public class MyCustomActionAttribute : ActionFilterAttribute
{
private readonly string param;
public MyCustomActionAttribute(string param)
{
this.param = param;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var paramValue = param;
base.OnActionExecuting(context);
}
}
and annotating your controller action like this:
[HttpGet]
[MyCustomAction("test123")]
public async Task<Details> GetDetails()
{
}

Request.IsAuthenticated function in ASP.NET 5

Is there an equivalent to Request.IsAuthenticated in ASP.NET 5 hidden somewhere or are we expected to loop through the user's identities and determine this ourselves?
If you just need to know if the User object is authenticated, this property should do the trick:
User.Identity.IsAuthenticated
If you need to prevent an action from being called by an unauthenticated user, the following attribute class works great.
public class BasicAuthAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
var user = filterContext.HttpContext.User;
if (user == null || !user.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
I use this in my base controller class as follows.
[BasicAuth]
public abstract class BaseAuthorizedController : Controller
You can also access the IsAuthenticated property from within your service layer by injecting an IHttpContextAccessor into it, like this:
public class MyService : IMyService {
private readonly IHttpContextAccessor httpContextAccesor;
public MyService(IHttpContextAccessor httpContextAccessor) {
this.httpContextAccessor = httpContextAccessor;
}
public void MyMethod() {
var isAuthenticated = this.httpContextAccessor.HttpContext.User.Identity.IsAuthenticated;
if (isAuthenticated) {
// Authenticated, do something!
}
}
}

Is it possible to use one generic/abstract service in ServiceStack?

I am developing a (hopefully) RESTful API using ServiceStack.
I noticed that most of my services look the same, for example, a GET method will look something like this:
try
{
Validate();
GetData();
return Response();
}
catch (Exception)
{
//TODO: Log the exception
throw; //rethrow
}
lets say I got 20 resources, 20 request DTOs, so I got about 20 services of the same template more or less...
I tried to make a generic or abstract Service so I can create inheriting services which just implement the relevant behavior but I got stuck because the request DTOs weren't as needed for serialization.
Is there any way to do it?
EDIT:
an Example for what I'm trying to do:
public abstract class MyService<TResponse,TRequest> : Service
{
protected abstract TResponse InnerGet();
protected abstract void InnerDelete();
public TResponse Get(TRequest request)
{
//General Code Here.
TResponse response = InnerGet();
//General Code Here.
return response;
}
public void Delete(TRequest request)
{
//General Code Here.
InnerDelete();
//General Code Here.
}
}
public class AccountService : MyService<Accounts, Account>
{
protected override Accounts InnerGet()
{
throw new NotImplementedException();//Get the data from BL
}
protected override void InnerDelete()
{
throw new NotImplementedException();
}
}
To do this in the New API we've introduced the concept of a IServiceRunner that decouples the execution of your service from the implementation of it.
To add your own Service Hooks you just need to override the default Service Runner in your AppHost from its default implementation:
public virtual IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new ServiceRunner<TRequest>(this, actionContext); //Cached per Service Action
}
With your own:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext); //Cached per Service Action
}
Where MyServiceRunner is just a custom class implementing the custom hooks you're interested in, e.g:
public class MyServiceRunner<T> : ServiceRunner<T> {
public override void OnBeforeExecute(IRequestContext requestContext, TRequest request) {
// Called just before any Action is executed
}
public override object OnAfterExecute(IRequestContext requestContext, object response) {
// Called just after any Action is executed, you can modify the response returned here as well
}
public override object HandleException(IRequestContext requestContext, TRequest request, Exception ex) {
// Called whenever an exception is thrown in your Services Action
}
}
Also for more fine-grained Error Handling options check out the Error Handling wiki page.
My solution was to add an additional layer where I can handle Logic per entity:
Base Logic Sample:
public interface IEntity
{
long Id { get; set; }
}
public interface IReadOnlyLogic<Entity> where Entity : class, IEntity
{
List<Entity> GetAll();
Entity GetById(long Id);
}
public abstract class ReadOnlyLogic<Entity> : IReadOnlyLogic<Entity> where Entity : class, IEntity, new()
{
public IDbConnection Db { get; set; }
#region HOOKS
protected SqlExpression<Entity> OnGetList(SqlExpression<Entity> query) { return query; }
protected SqlExpression<Entity> OnGetSingle(SqlExpression<Entity> query) { return OnGetList(query); }
#endregion
public List<Entity> GetAll()
{
var query = OnGetList(Db.From<Entity>());
return Db.Select(query);
}
public Entity GetById(long id)
{
var query = OnGetSingle(Db.From<Entity>())
.Where(e => e.Id == id);
var entity = Db.Single(query);
return entity;
}
}
Then we can use hooks like:
public interface IHello : IReadOnlyLogic<Hello> { }
public class HelloLogic : ReadOnlyLogic<Hello>, IHello
{
protected override SqlExpression<Hello> OnGetList(SqlExpression<Hello> query)
{
return query.Where(h => h.Name == "Something");
}
}
Finally our service only calls our logic:
public class MyServices : Service
{
IHello helloLogic;
public object Get()
{
return helloLogic.GetAll();
}
}