ASP.Net Core WebApi - storing values from ActionFilter to access in controller - asp.net-core

In an ASP.Net Core WebApp I want to use an ActionFilter and send information from the ActionFilter to the controller it is applied to.
MVC
For MVC I can do this
ActionFilter
public class TenantActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
//Using some sneaky logic to determine current tenant from domain, not important for this example
int tenantId = 1;
Controller controller = (Controller)context.Controller;
controller.ViewData["TenantId"] = tenantId;
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
Controller
public class TestController : Controller
{
[ServiceFilter(typeof(TenantActionFilter))]
public IActionResult Index()
{
int tenantId = ViewData["TenantId"];
return View(tenantId);
}
}
It works and I can pass data back to the controller via ViewData - great.
WebApi
I want to do the same for WebApi Controllers.
The actionFilter itself can be applied, runs etc - but I cannot write to ViewData, because WebAPI inherits from ControllerBase - not from Controller (like MVC).
Question
How can I push data from my ActionFilter back to the calling ControllerBase, similar to MVC?
Notes
ASP.Net Core 2.2 being used, but I would be surprised if a solution would not be usable in all of .Net Core.

...So I found the answer when I was almost done writing the question, so here goes...
The answer is the HttpContext.Items collection
ActionFilter
public class TenantActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
int tenantId = 1;
var controller = (ControllerBase)context.Controller;
controller.HttpContext.Items.Add("TenantId", tenantId);
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
Controller
public class TestApiController : ControllerBase
{
[ServiceFilter(typeof(TenantActionFilter))]
public SomeClass Get()
{
int tenantId;
if (!int.TryParse(HttpContext.Items["TenantId"].ToString(), out tenantId))
{
tenantId = -1;
}
return new SomeClass();
}
}

Related

Session.IsNewSession in ASP.NET Core

I am migrating an ASP.NET MVC application to ASP.NET Core 3.1.
And I have a code to check if the session was timed out in my controller, like this:
if (Session.IsNewSession) {
How can I check it in ASP.NET Core?
Thanks
The default implementation of ISession is DistributedSession. This does not expose any property for IsNewSession although its constructor accepts a parameter named isNewSessionKey. So you can use reflection to get that private field of _isNewSessionKey to check it. But that way is not very standard, the name may be changed in future without notifying you any design-time error.
You have several points to intercept and get the info here. The first point is to create a custom ISessionStore (default by DistributedSessionStore) to intercept the call to ISessionStore.Create which gives access to isNewSessionKey. You can capture that value into a request feature just like how the framework set the ISessionFeature after creating the session. Here's the code:
//create the feature interface & class
public interface ISessionExFeature {
bool IsNewSession { get; }
}
public class SessionExFeature : ISessionExFeature {
public SessionExFeature(bool isNewSession){
IsNewSession = isNewSession;
}
public bool IsNewSession { get; }
}
//the custom ISessionStore
public class CustomDistributedSessionStore : DistributedSessionStore, ISessionStore
{
readonly IHttpContextAccessor _httpContextAccessor;
public CustomDistributedSessionStore(IDistributedCache cache,
ILoggerFactory loggerFactory,
IHttpContextAccessor httpContextAccessor) : base(cache, loggerFactory)
{
_httpContextAccessor = httpContextAccessor;
}
ISession ISessionStore.Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
{
var httpContext = _httpContextAccessor.HttpContext;
if(httpContext != null)
{
var sessionExFeature = new SessionExFeature(isNewSessionKey);
httpContext.Features.Set<ISessionExFeature>(sessionExFeature);
}
return Create(sessionKey, idleTimeout, ioTimeout, tryEstablishSession, isNewSessionKey);
}
}
//register the custom ISessionStore inside Startup.ConfigureServices
services.Replace(new ServiceDescriptor(typeof(ISessionStore), typeof(CustomDistributedSessionStore), ServiceLifetime.Transient));
//an extension method to help get the ISessionExFeature conveniently
public static class SessionExFeatureHttpContextExtensions {
public static bool HasNewSession(this HttpContext context){
return context.Features.Get<ISessionExFeature>()?.IsNewSession ?? false;
}
}
To use it in your code:
if (HttpContext.HasNewSession()) {
//...
}
Another point to intercept and get the info is customize both the ISessionStore and ISession. Which means you create a sub class of DistributedSession and expose the property for IsNewSession. That may require more code but it looks more like the old way of getting the info (directly from the Session not kind of via an extension method on HttpContext).

ASP.NET Core 3.1 Base Controller Get Current User expires

we have an asp.net core MVC app hosted in IIS with Windows Authentication and we have a base class on the controllers where we get the authenticated user, but sometimes when doing AJAX posts, it returns NULL, is this the right place to get the user? I suspect it is because of inactivity, but we had this problem happen only after 7 minutes of inactivity, thanks!
public class BaseController : Controller
{
public string ShortName { get; set; }
public BaseController(IOptions<AppSettings> app_settings, IHttpContextAccessor contextAccessor)
{
ShortName = contextAccessor.HttpContext.User.Identity.Name.Substring(contextAccessor.HttpContext.User.Identity.Name.LastIndexOf('\\') + 1);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
}
}

How to use polymorphism one method on controller actions

I tried to convert ASP.NET WEB API to ASP.NET CORE WEB API and have errors
My code in ASP.NET WebAPI
public class TestController : ApiController
{
// GET /test
public object Get()
{
return "get";
}
// GET /test?id={id}
public object Get(string id)
{
return id;
}
// GET /test?id={id}&anyParam={anyParam}
public object Get(string id, string anyParam)
{
return id + anyParam;
}
}
config.Routes.MapHttpRoute("Controller", "{controller}");
Try to convert it to ASP.NET Core 2.1 / 3.0
[ApiController]
[Route("{controller}")]
public class TestController : ControllerBase
{
// GET /test
public object Get()
{
return "get";
}
// GET /test?id={id}
public object Get(string id)
{
return id;
}
// GET /test?id={id}&anyParam={anyParam}
public object Get(string id, string anyParam)
{
return id + anyParam;
}
}
services.AddControllers();
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
And i have in ASP.NET Core
AmbiguousMatchException: The request matched multiple endpoints
The sensible solution is just have one method that takes three parameters.
But, sensible solutions don't make for the most interesting stackoverflow answers, so here is how you can do this with two custom attributes, one which states the parameters that are required, and another which states which parameters are excluded:
public class RequireRequestParameterAttribute : ActionMethodSelectorAttribute
{
private readonly string[] _requiredNames;
public RequireRequestParameterAttribute(params string[] names)
{
this._requiredNames = names;
}
public override bool IsValidForRequest(
RouteContext routeContext,
ActionDescriptor action
) =>
this._requiredNames
.All(
routeContext
.HttpContext
.Request
.Query
.ContainsKey
);
}
public class DisallowRequestParameterAttribute : ActionMethodSelectorAttribute
{
private readonly string[] _forbiddenNames;
public DisallowRequestParameterAttribute(params string[] names)
{
this._forbiddenNames = names;
}
public override bool IsValidForRequest(
RouteContext routeContext,
ActionDescriptor action
) =>
!(this._forbiddenNames
.Any(
routeContext
.HttpContext
.Request
.Query
.ContainsKey
)
);
}
Now you can apply the attributes as follows:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
// GET test
public object Get()
{
return "Get";
}
// GET test?id={id}
[RequireRequestParameter("id")]
[DisallowRequestParameter("anyParam")]
public object Get(string id)
{
return id;
}
// GET test?id={id}&anyParam={anyParam}
[RequireRequestParameter("id", "anyParam")]
public object Get(string id, string anyParam)
{
return $"{id}: {anyParam}";
}
}
This means if you add another method with a third parameter, you have the maintenance burden of adding or modifying the DisallowRequestParameter attribute on the other methods.
I look your generated urls on actions and they are both /test which cause AmbiguousMatchException because your parameters are GET and are optional.
I think you can have same names on actions but you need define different ROUTE attribute (diff urls) on actions. Eg. you can not use default route with polymorphism on controller actions.
[Route("Home/About")]
MVC controllers Mapping of controllers now takes place inside
UseEndpoints.
Add MapControllers if the app uses attribute routing.
Source
https://learn.microsoft.com/cs-cz/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.0#attribute-routing
Thanks to daremachine with his answer I was able to find information on Google
First step in ASP.NET Core we need class which inherit ActionMethodSelectorAttribute
public class RequireRequestValueAttribute : ActionMethodSelectorAttribute
{
public RequireRequestValueAttribute(string name, string value = null)
{
Name = name;
Value = value;
}
public string Name { get; }
public string Value { get; }
public StringComparison ComparisonType { get; } = StringComparison.OrdinalIgnoreCase;
private bool ValueIsValid(object value)
{
return ValueIsValid(value?.ToString());
}
private bool ValueIsValid(string value)
{
if (Value == null)
{
return true;
}
return string.Equals(value, Value, ComparisonType);
}
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
var value = default(object);
if (routeContext.RouteData.Values.TryGetValue(Name, out value) && ValueIsValid(value))
return true;
if (routeContext.RouteData.DataTokens.TryGetValue(Name, out value) && ValueIsValid(value))
return true;
if (routeContext.HttpContext.Request.Query.ContainsKey(Name))
{
var values = routeContext.HttpContext.Request.Query[Name];
if (values.Count <= 0)
{
if (ValueIsValid(null))
return true;
}
else if (values.Any(v => ValueIsValid(v)))
return true;
}
return false;
}
}
Then we can add to question methods [RequireRequestValue("")], the controller will look like this
[ApiController]
[Route("{controller}")]
public class TestController : ControllerBase
{
// GET /test
public object Get()
{
return "get";
}
// GET /test?id={id}
[RequireRequestValue("id")]
public object Get(string id)
{
return id;
}
}
But it can't polymorphism two similar fields, type id in my question
For asp net core 2. If you try to implement the same logic as was in web api controllers then use Microsoft.AspNetCore.Mvc.WebApiCompatShim. This nuget package provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. Please check this answer. Starting with ASP.NET Core 3.0, the Microsoft.AspNetCore.Mvc.WebApiCompatShim package is no longer available.

dependency injection into view model class in asp.net core

I am using the following DTO class in one of my api controller class, in an asp.net core application.
public class InviteNewUserDto: IValidatableObject
{
private readonly IClientRepository _clientRepository;
public InviteNewUserDto(IClientRepository clientRepository)
{
_clientRepository = clientRepository;
}
//...code omitted for brevity
}
This is how I using it in a controller
[HttpPost]
public async Task<IActionResult> RegisterUser([FromBody] InviteNewUserDto model)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
//...omitted for brevity
}
But I am getting a System.NullReferenceException in the DTO class
This is happening since dependency injection is not working in the DTO class.
How can I fix this ?
DI will not resolve dependences for ViewModel.
You could try validationContext.GetService in Validate method.
public class InviteNewUserDto: IValidatableObject
{
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
IClientRepository repository = (IClientRepository)validationContext.GetService(typeof(IClientRepository));
return null;
}
}
Did you register ClientRepository in startup.cs?
public void ConfigureServices(IServiceCollection services)
{
...
// asp.net DI needs to know what to inject in place of IClientRepository
services.AddScoped<IClientRepository, ClientRepository>();
...
}

Injecting repository in custom filter.net core mvc

I have app written in asp.net core MVC c#
I have written a custom filter as:
public class CustomFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
//need to call my sqlrepository here
}
}
How can I call my SQL repository inside my custom filter?
I have my controller as:
public class HomeController
{
private IMySqlRepository _repo;
public HomeController(IMySqlRepository myRepo)
{
_repo= myRepo;
}
}
In my home controller, I can call this fine by above code.
Also just FYI I register this repo in my startup as:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMySqlRepository , MySqlRepository >();
}