ServiceStack IOC not injecting property in Attribute (object is null) - asp.net

I'm trying to log/persist all my requests/responses, and thought that I give it a try with a global attribute, but when I go to actually using the repo, it's null? Is this possible?
Are there other ways to achieve what I'm looking to do?
Thank you,
Stephen
Attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class LogRequestAttribute : RequestFilterAttribute
{
public IRepository Repo { get; set; }
public LogRequestAttribute(ApplyTo applyTo)
: base(applyTo)
{
this.Priority = -200;
}
public LogRequestAttribute()
: this(ApplyTo.All) {}
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{
try
{
// Convert the req obj into something that can be persisted...
Repo.LogRequest("Logging the rquest");
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
}
}
}
AppHost Config
public override void Configure(Container container)
{
//Set JSON web services to return idiomatic JSON camelCase properties
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
//Show StackTrace in Web Service Exceptions
SetConfig(new EndpointHostConfig { DebugMode = true });
//Register any dependencies you want injected into your services
container.Register<ICacheClient>(new MemoryCacheClient());
/* // Redis
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager());
container.Register<IRepository>(c => new Repository(c.Resolve<IRedisClientsManager>()));*/
container.Register<IRepository>(new Repository());
container.Register<IBusinessService>(new BusinessService());
//Configure Custom User Defined REST Paths for your services
/*ConfigureServiceRoutes();*/
//Add a request filter to check if the user has a session initialized
/*this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
var sessionId = httpReq.GetCookieValue("user-session");
if (sessionId == null)
{
httpResp.ReturnAuthRequired();
}
});*/
RequestFilters.Add((httpReq, httpResp, requestDto) => new LogRequestAttribute().Execute(httpReq, httpResp, requestDto));
}
Repository
public interface IRepository
{
void LogRequest(string request);
void LogResponse(string request);
}
public class Repository : IRepository
{
private static readonly ILog Log = LogManager.GetLogger("API.Repository");
public Repository()
{
}
public void LogRequest(string request)
{
Log.Debug(request);
}
public void LogResponse(string request)
{
Log.Debug(request);
}
}
Updated
//Add a 'global' request filter
this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
/* Code here */
});
//Add a 'global' response filter
this.ResponseFilters.Add((httpReq, httpResp, responseDto) =>
{
/* Code here */
});

If you're trying to log requests in ServiceStack you should look to see if Request Logger plugin is useful. The RequestLogsFeature Plugin allows you to use your own custom IRequestLogger instead of the InMemoryRollingRequestLogger that's used by default.
Filter Attributes
Although you've defined a Request Filter attribute correctly you're not applying it correctly, which should be used just like any other C# Attribute (i.e. decorated). Filter Attributes can only be decorated on either the Service Type, its Request DTO or a Service Action where it is only run to the scope they are applied to.
Global Request Filters
There is no Global Request Filter Attribute, the Global Request filters only let you specify a delegate to get executed, which is all that's happening here:
RequestFilters.Add((httpReq, httpResp, requestDto) =>
new LogRequestAttribute().Execute(httpReq, httpResp, requestDto));
A new instance of the LogRequestAttribute type is constructed inline (and as seen above, is not resolved from the IOC) so it is not auto-wired. The fact that the method you're calling is an instance of a FilterAttribute is irrelevant since all the C# delegate is calling is a method on an empty LogRequestAttribute instance.
If registering a global filter inside Configure() you can access the container directly, e.g:
RequestFilters.Add((httpReq, httpResp, requestDto) =>
container.Resolve<IRepository>().LogRequest("Logging the request"));
Anywhere else, you can access ServiceStack's IOC with the singleton: AppHostBase.Resolve<T>().

Related

Is it possile to return .NET object from controller to middleware

I was working on one of the requirements, where I need to modify result data in middleware (not any MVC Filters due to some other services injected through middleware).
In middleware I was getting data in json format and then deserializing that data then updating that data and finally serializing to JSON and sending it back as a response.
I don't want to serialize data in MVC pipeline so I tried to remove output formator but that didn't work for me and throwing error.
services.AddControllers(options =>
{
options.OutputFormatters.Clear();
});
Is there any solution to get the .Net object in the pipeline and modify that object (as we do in MVC filter) and then serialize at last?
I am not sure whether it fits your requirements but you can use HttpContext to store some data in the scope of the request. There is a 'Items' key-value collection.
Beside the other suggestion to use Items of HttpContext, I want to note that you can inject services into Action Filters:
public class ResultFilter : IActionFilter
{
// Inject anything you want
IHostEnvironment env;
public ResultFilter(IHostEnvironment env)
{
this.env = env;
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is OkObjectResult result)
{
result.Value = JsonSerializer.Serialize(new
{
Value = result.Value,
Environment = this.env.EnvironmentName,
});
}
}
public void OnActionExecuting(ActionExecutingContext context) { }
}
Register to DI Builder:
services.AddScoped<ResultFilter>();
Apply to action/controller:
[HttpGet, Route("/test"), ServiceFilter(typeof(ResultFilter))]
public IActionResult ReturnOk()
{
return this.Ok(new
{
Value = 1,
});
}
Testing by accessing the URL:
{"Value":{"Value":1},"Environment":"Development"}
Another alternative is to use DI service with Scoped lifetime.
Scoped objects are the same for a given request but differ across each new request.
Service:
public interface IMyRequestDataService
{
object? MyData { get; set; }
}
public class MyRequestDataService : IMyRequestDataService
{
public object? MyData { get; set; }
}
Register to DI:
services.AddScoped<IMyRequestDataService, MyRequestDataService>();
Set data in Controller:
readonly IMyRequestDataService dataService;
public TestController(IMyRequestDataService dataService)
{
this.dataService = dataService;
}
[HttpGet, Route("/test-scoped")]
public IActionResult ReturnObj()
{
this.dataService.MyData = new
{
Value = 1,
};
return this.Ok();
}
Your middleware that consumes it:
class CustomMiddleware
{
readonly RequestDelegate next;
public CustomMiddleware(RequestDelegate next)
{
this.next = next;
}
// Add DI Services here
public async Task InvokeAsync(HttpContext httpContext, IMyRequestDataService dataService, IHostEnvironment env)
{
await this.next(httpContext);
// Data should be here
if (dataService.MyData != null)
{
// Do something with it
await httpContext.Response.WriteAsJsonAsync(new
{
Data = dataService.MyData,
Env = env.EnvironmentName,
});
}
}
}
// Register it:
app.UseMiddleware<CustomMiddleware>();
// Make sure it's before the Controller middleware since we wrap it around the next()
// ...
app.MapControllers();
Test with the URL:
{"data":{"value":1},"env":"Development"}
You can store data in HTTP context items.
In controller action:
Request.HttpContext.Items.Add("SomeKey", data);
In middleware:
object data = httpContext.Items["SomeKey"];

Leveraging user context in an IHostedService via DI

I have a series of class libraries that are used in asp.net-core middleware, and in an IHostedService.
To fetch the user context, I can inject IHttpContextAccessor to grab the HttpContext user:
public class MyLibrary
{
public MyLibrary(IHttpContextAccessor accessor)
{
// set the accessor - no problem
}
public async Task DoWorkAsync(SomeObject payload)
{
// get the user from the accessor
// do some work
}
}
To be a little more abstract, I have an IUserAccessor with an HttpUserAccessor implementation:
public class HttpUserAccessor: IUserAccessor
{
IHttpContextAccessor _httpaccessor;
public HttpUserAccessor(IHttpContextAccessor accessor)
{
_httpaccessor = accessor;
}
public string GetUser()
{
// return user from _httpaccessor
}
}
and then MyLibrary does not need an IHttpContextAccessor dependency:
public class MyLibrary
{
public MyLibrary(IUserAccessor accessor)
{
// set the accessor - no problem
}
public async Task DoWorkAsync(SomeObject payload)
{
// get the user from the accessor
// do some work
}
}
My IHostedService is popping message from a queue, where the message includes:
a user context, and
a serialized SomeObject to pass to MyLibrary.DoWorkAsync
So, something like:
public class MyHostedService : IHostedService
{
IServiceScopeProvider _serviceScopeFactory;
public MyHostedService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = servicesScopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{ ... }
public Task StopAsync(CancellationToken cancellationToken)
{ ... }
public async Task ExecuteAsync(CancellationToken stoppingToken)
{
foreach (var message in queue)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
// todo: tell IUserAccessor what message.User is!
var payload = // create a SomeObject from the queue message
var mylibrary = _services.GetRequiredService<MyLibrary>();
await myLibrary.DoWorkAsync(payload);
}
}
}
}
So, my question is, how does MyHostedService store message.User in such a way that a custom IUserAccessor can access it in a thread-safe manner via DI?
how does MyHostedService store message.User in such a way that a custom IUserAccessor can access it in a thread-safe manner via DI?
The thing you're looking for is AsyncLocal<T> - it's like a thread-local variable but scoped to a (possibly asynchronous) code block instead of a thread.
I tend to prefer a "provider" + "accessor" pairing for this: one type that provides the value, and a separate type that reads the value. This is logically the same thing as a React Context in the JS world, though the implementation is quite different.
One tricky thing about AsyncLocal<T> is that you need to overwrite its value on any change. In this case, that's not really a problem (no message processing will want to update the "user"), but in the general case it's important to keep in mind. I prefer storing immutable types in the AsyncLocal<T> to ensure they aren't mutated directly instead of overwriting the value. In this case, your "user" is a string, which is already immutable, so that's perfect.
First, you'll need to define the actual AsyncLocal<T> to hold the user value and define some low-level accessors. I strongly recommend using IDisposable to ensure the AsyncLocal<T> value is unset properly at the end of the scope:
public static class AsyncLocalUser
{
private static AsyncLocal<string> _local = new AsyncLocal<string>();
private static IDisposable Set(string newValue)
{
var oldValue = _local.Value;
_local.Value = newValue;
// I use Nito.Disposables; feel free to replace with another IDisposable implementation.
return Disposable.Create(() => _local.Value = oldValue);
}
private static string Get() => _local.Value;
}
Then you can define a provider:
public static class AsyncLocalUser
{
... // see above
public sealed class Provider
{
public IDisposable SetUser(string value) => Set(value);
}
}
and the accessor is similarly simple:
public static class AsyncLocalUser
{
... // see above
public sealed class Accessor : IUserAccessor
{
public string GetUser() => Get();
}
}
You'll want to set up your DI to point IUserAccessor to AsyncLocalUser.Accessor. You can also optionally add AsyncLocalUser.Provider to your DI, or you can just create it directly.
Usage would go something like this:
foreach (var message in queue)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var userProvider = new AsyncLocalUser.Provider(); // (or get it from DI)
using (userProvider.SetUser(message.User))
{
var payload = // create a SomeObject from the queue message
var mylibrary = _services.GetRequiredService<MyLibrary>();
await myLibrary.DoWorkAsync(payload);
}
}
}

IConfigureOptions<T> is not creating scoped options

Typically Options are singleton. However i am building options from the database, and one of the Options property is password which keep changing every month. So i wanted to create Scoped instance of Options. I am using IConfigureOptions<T> like below to build Options from the database
public class MyOptions
{
public string UserID {get;set;}
public string Password {get;set;
}
public class ConfigureMyOptions : IConfigureOptions<MyOptions>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConfigureMyOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(MyOptions options)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
using (var dbContext = provider.GetRequiredService<MyDBContext>())
{
options.Configuration = dbContext.MyOptions
.SingleOrDefault()
.Select(x => new MyOptions()
{
UserID = x.UserID,
Password = x.Password
});
}
}
}
}
Use it in controller
public class HomeController : BaseController
{
private readonly MyOptions _options;
public HomeController(IOptions<MyOptions> option)
{
_options = option.Value;
}
[HttpGet]
[Route("home/getvalue")]
public string GetValue()
{
// do something with _options here
return "Success";
}
}
I want to create an instance of MyOptions for every new request so register it as Scoped in startup.cs
services.AddScoped<IConfigureOptions<MyOptions>, ConfigureMyOptions>();
However, when i put debugger inside ConfigureMyOptions's Configure method it only gets hit once for the first request. For next request onward the container returns the same instance (like singleton).
How do i set the scope here so MyOptions will get created for each request?
Use IOptionsSnapshot instead of IOptions in your controller and it will recreate options per request.
Why doesn't work with IOptions:
.AddOptions extension method of Configuration API registers the OptionsManager instance as a singlethon for IOptions<>
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
and OptionsManager class uses caching internally:
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
// Store the options in our instance cache
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
The following issue on github helped to find above: OptionsSnapshot should always be recreated per request

How to rewrite code to use IAuthorizationFilter with dependency injection instead of AuthorizeAttribute with service location in Asp Net Web Api?

I have the custom AuthorizeAttribute where I need to use one of the business layer services to validate some data in the database before giving user a permission to view the resource. In order to be able to allocate this service within the my AuthorizeAttribute I decided to use service location "anti-pattern", this is the code:
internal class AuthorizeGetGroupByIdAttribute : AuthorizeAttribute
{
private readonly IUserGroupService _userGroupService;
public AuthorizeGetGroupByIdAttribute()
{
_userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
}
//In this method I'm validating whether the user is a member of a group.
//If they are not they won't get a permission to view the resource, which is decorated with this attribute.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
return _userGroupService.IsUserInGroup(currentUserId, groupId);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContex)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContex);
}
else
{
actionContex.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
I have couple of other attributes like this in my application. Using service locator is probably not a good approach. After searching the web a little bit I found some people suggesting to use IAuthorizationFilter with dependency injection instead. But I don't know how to write this kind of IAuthorizationFilter. Can you help me writing IAuthorizationFilter that will do the same thing that the AuthorizeAttribute above?
So after struggling for a while I think I managed to resolve this issue. Here are the steps you have to do in order to that:
1) First you have to make GetGroupByIdAttribute passive, and by passive I mean an empty attribute without any logic within it (it will be used strictly for decoration purposes)
public class GetGroupByIdAttribute : Attribute
{
}
2) Then you have to mark a controller method, for which you want to add authorization, with this attribute.
[HttpPost]
[GetGroupById]
public IHttpActionResult GetGroupById(int groupId)
{
//Some code
}
3) In order to write your own IAuthorizationFilter you have to implement its method ExecuteAuthorizationFilterAsync. Here is the full class (I included comments to guide you through the code):
public class GetGroupByIdAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get; set; }
private readonly IUserGroupService _userGroupService;
//As you can see I'm using a constructor injection here
public GetGroupByIdAuthorizationFilter(IUserGroupService userGroupService)
{
_userGroupService = userGroupService;
}
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//First I check whether the method is marked with the attribute, if it is then check whether the current user has a permission to use this method
if (actionContext.ActionDescriptor.GetCustomAttributes<GetGroupByIdAttribute>().SingleOrDefault() != null)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
//If the user is not allowed to view view the resource, then return 403 status code forbidden
if (!_userGroupService.IsUserInGroup(currentUserId, groupId))
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
}
}
//If this line was reached it means the user is allowed to use this method, so just return continuation() which basically means continue processing
return continuation();
}
}
4) The last step is to register your filter in the WebApiConfig.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Here I am registering Dependency Resolver
config.DependencyResolver = ServiceLocator.Instance.DependencyResolver;
//Then I resolve the service I want to use (which should be fine because this is basically the start of the application)
var userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
//And finally I'm registering the IAuthorizationFilter I created
config.Filters.Add(new GetGroupByIdAuthorizationFilter(userGroupService));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Now, if needed, I can create additional IActionFilters that use IUserGroupService and then inject this service at the start of the application, from WebApiConfig class, into all filters.
Perhaps try it like shown here:
Add the following public method to your class.
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// gets the dependecies from the serviceProvider
// and creates an instance of the filter
return new GetGroupByIdAuthorizationFilter(
(IUserGroupService )serviceProvider.GetService(typeof(IUserGroupService )));
}
Also Add interface IFilterMetadata to your class.
Now when your class is to be created the DI notices that there is a CreateInstance method and will use that rather then the constructor.
Alternatively you can get the interface directly from the DI in your method by calling
context.HttpContext.Features.Get<IUserGroupService>()

Where are plug-ins supposed to be added in ServiceStack

So simple yet I can't find any info or examples that explain exacty where this should happen. I'm guessing at this point that it should be in the Configure method.
Thank you,
Stephen
Global
public class AppHost : AppHostBase
{
public AppHost() : base("Web Services", typeof(ContactsService).Assembly) { }
public override void Configure(Container container)
{
//Set JSON web services to return idiomatic JSON camelCase properties
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
//Show StackTrace in Web Service Exceptions
SetConfig(new EndpointHostConfig { DebugMode = true });
//Register any dependencies you want injected into your services
container.Register<ICacheClient>(new MemoryCacheClient());
/* // Redis
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager());
container.Register<IRepository>(c => new Repository(c.Resolve<IRedisClientsManager>()));*/
container.Register<IRepository>(new Repository());
container.Register<IBusinessService>(new BusinessService());
//Configure Custom User Defined REST Paths for your services
/*ConfigureServiceRoutes();*/
//Add a request filter to check if the user has a session initialized
/*this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
var sessionId = httpReq.GetCookieValue("user-session");
if (sessionId == null)
{
httpResp.ReturnAuthRequired();
}
});*/
RequestFilters.Add((httpReq, httpResp, requestDto) => new LogRequestAttribute().Execute(httpReq, httpResp, requestDto));
Plugins.Add(new SwaggerFeature());
}
public static void Start()
{
new AppHost().Init();
}
}
Updated
public AppHost() : base("Web Services", typeof(ContactsService).Assembly) { }
public override void Configure(Container container)
{
....
ConfigurePlugins();
}
private void ConfigurePlugins()
{
Plugins.Add(new ProtoBufFormat());
Plugins.Add(new RequestLogsFeature());
Plugins.Add(new SwaggerFeature());
}
private void ConfigureServiceRoutes()
{
}
public static void Start()
{
new AppHost().Init();
}
There is no info because Plugins in ServiceStack can be added anywhere inside your AppHost.Configure() method. This is true of all ServiceStack configuration and registration of dependencies, services, filters, etc.
It doesn't matter where in the AppHost.Configure() method they're added because they're only Initialized by ServiceStack after it has been called.
They are however initialized (i.e. IPlugin.Register() is called) in the same order that they were added.

Resources