For Razor Pages using ASP.NET Core is there any way to create a catch-all handler for all verbs instead of having separate OnGet(), OnPost(). The handler will need access to HttpContext and Request objects (not provided in the constructor)
Instead of
public class ExampleModel : PageModel
{
public void OnGet()
{
//do something
}
public void OnPost()
{
//do something
}
}
Something like the following
public class ExampleModel : PageModel
{
public void OnAll()
{
//code executes for POST, PUT, GET, ... VERBS
}
}
Also would work is just something generic that would execute before or after (with context) each request
Also would work is just something generic that would execute before or after (with context) each request
Taking the above into account you probably want to use filters. Declaration:
public class DefaultFilterAttribute : ResultFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("Here we go");
base.OnResultExecuted(context);
}
}
In case you want to see this behavior on a single page only:
[DefaultFilter]
public class IndexModel : PageModel
{
}
In case you need this filter to be applied on all pages (Startup.cs):
services.AddMvcOptions(options =>
{
options.Filters.Add(typeof(DefaultFilterAttribute));
});
If you want to execute a group of commands for all request methods, you can use the constractor of the PageModel:
public class IndexModel : PageModel
{
public IndexModel()
{
// This will be executed first
}
public void OnGet()
{
}
}
New solution
I have an other solution for you. Create a class which will inherit from PageModel where you will catch all the different request methods and call a new virtual method.
public class MyPageModel : PageModel
{
public virtual void OnAll()
{
}
public void OnGet()
{
OnAll();
}
public void OnPost()
{
OnAll();
}
}
Now change your PageModel class so that it will inherit from the new class that you created. In your class you can overrice the OnAll method in order to execute your common code.
public class TestModel : MyPageModel
{
public override void OnAll()
{
// Write your code here
}
}
Related
I'm creating ASP.NET Core 3.1 app, using SPA for front end. So I decided to create custom Authentication & Authorization. So I created custom attributes to give out and verify JWTs.
Lets say it looks like this:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
//Checking Headers..
using (var EF = new DatabaseContext)
{
user = EF.User.Where(p => (p.Email == username)).FirstOrDefault();
}
filterContext.HttpContext.Response.Headers.Add(
"AccessToken",
AccessToken.CreateAccessToken(user));
}
}
Everything was Okay, but my DatabaseContext, looked like this:
public class DatabaseContext : DbContext
{
public DbSet<User> User { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("ConnectionString");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//....
}
}
I wanted to take Connection string from Appsettings.json and maybe use Dependency injection. I
Changed Startup.cs to look like this:
//...
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<DatabaseContext>(
options => options.UseMySQL(Configuration["ConnectionStrings:ConnectionString"]));
services.Add(new ServiceDescriptor(
typeof(HMACSHA256_Algo), new HMACSHA256_Algo(Configuration)));
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
//...
Changed Database Context class to this:
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
///..
}
}
In Controllers I injected DB context and everything works. It looks like this:
[ApiController]
[Route("API")]
public class APIController : ControllerBase
{
private DatabaseContext EF;
public WeatherForecastController(DatabaseContext ef)
{
EF = ef;
}
[HttpGet]
[Route("/API/GetSomething")]
public async Task<IEnumerable<Something>> GetSomething()
{
using(EF){
//.. this works
}
}
}
But my custom Attribute doesn't work no more. I can't declare new Database context, because it needs DatabaseContextOptions<DatabaseContext> object to declare, so how do I inject DBContext to Attribute as I did to Controller?
This doesn't work:
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
private DatabaseContext EF;
public AuthLoginAttribute(DatabaseContext ef)
{
EF = ef;
}
public async void OnAuthorization(AuthorizationFilterContext filterContext)
{
using(EF){
}
}
}
this works with controller, but with attribute complains about there not being constructor with 0 arguments.
What you can do is utilize the RequestServices:
[AttributeUsage(AttributeTargets.Method)]
public class AuthLoginAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var dbContext = context.HttpContext
.RequestServices
.GetService(typeof(DatabaseContext)) as DatabaseContext;
// your code
}
}
If you allow me to add two comments to your code:
Try not to use async void because in the event of an exception you will be very confused what is going on.
There is no need to wrap injected DbContext in a using statement like this using(EF) { .. }. You will dispose it early and this will lead to bugs later in the request. The DI container is managing the lifetime for you, trust it.
Is it possible use one interface for all dbcontext in enterprise app.I want create one interface and base context with partial and use this for all dbcontext.
public interface IComBaseDbContext : IDisposable // ComDbContext,
{
DbSet<TEntity> Set<TEntity>() where TEntity : class;
void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;...}
public partial class ComBaseDbContext:DbContext
{
public ComBaseDbContext()
{
}
public ComBaseDbContext(DbContextOptions<ComBaseDbContext> options) : base(options)
{
}
}
public partial class ComBaseDbContext : IComBaseDbContext
{
public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
{
Set<TEntity>().AddRange(entities);
}
public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
{
Set<TEntity>().RemoveRange(entities);
}...
}
and use in :
public partial class ComDbContext : ComBaseDbContext
{
public ComDbContext()
{
}
public ComDbContext(DbContextOptions<ComBaseDbContext> options) : base(options)
{
}
public virtual DbSet<Address> Address { get; set; }...}
and add ComDbContext to service :
services.AddScoped<IComBaseDbContext, ComDbContext1>();
services.AddScoped<IComBaseDbContext, ComDbContext2>();
when use this pattern ComDbContext2 replace to ComDbContext1 .
how to change code to use this.how to use interface .Is this true?
Option 1 :
If you want to define the context for each DI in controllers, create a generic interface:
public interface IComBaseDbContext<TContext> where TContext : DbContext
{
// ...
}
Implement custom generic context:
public class ComDbContext<TCotnext> : IComBaseDbContext<TContext>
where TContext : DbContext
{
private readonly TContext _context;
public ComDbContextA(TContext context)
{
_context = context ?? throw new NotImplementedException(nameof(context));
}
public void AddRange<TEntity>(IEnumerable<TEntity> entities)
where TEntity : class
{
_context.Set<TEntity>().AddRange(entities);
// ...
}
}
Register in startup:
services.AddScoped(typeof(IComBaseDbContext<>), typeof(ComDbContext<>));
Use in controllers:
public class HomeController : Controller
{
private readonly IComBaseDbContext<AppContextA> _contextA;
public HomeController(IComBaseDbContext<AppContextA> contextA)
{
_contextA = contextA;
}
public IActionResult Index()
{
_context.AddRange<..>(...);
}
}
Option 2 :
If you want to have a common default context, and optionally change it in some controllers, create one generic and one standard interface.
public interface IComBaseDbContext<TContext> : IComBaseDbContext
where TContext : DbContext
{
}
public interface IComBaseDbContext
{
// ...
}
Then register in startup:
// This will use the default AppContextA
service.AddScoped<IComBaseDbContext, ComDbContext<AppContextA>>();
// This can have a generic AppContext
service.AddScoped(typeof(IComBaseDbContext<>), typeof(ComDbContext<>));
Use default context in controllers:
public class HomeController : Controller
{
private readonly IComBaseDbContext _context;
public HomeController(IComBaseDbContext context)
{
_contextA = contextA;
}
public IActionResult Index()
{
_context.AddRange<..>(...);
}
}
Use a genric context:
public class HomeController : Controller
{
private readonly IComBaseDbContext<AppContextB> _contextB;
public HomeController(IComBaseDbContext<AppContextB> contextB)
{
_contextB = contextB;
}
public IActionResult Index()
{
_context.AddRange<..>(...);
}
}
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()
{
}
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);
}
}
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();
}
}