I have a brand new MVC4 project on which I have installed the ServiceStack MVC starter pack (version 4.0.12 from MyGET) to bootstrap the usage of the service stack sessions.
In my AppHost my custom session is configured as:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(config)
}));
The custom session looks like this for testing purposes:
public class CustomUserSession : AuthUserSession
{
public string Hello { get; set; }
}
And the ICacheClient is registered as a redis client:
// register the message queue stuff
var redisClients = config.Get("redis-servers", "redis.local:6379").Split(',');
var redisFactory = new PooledRedisClientManager(redisClients);
var mqHost = new RedisMqServer(redisFactory, retryCount: 2);
container.Register<IRedisClientsManager>(redisFactory); // req. to l
container.Register<IMessageFactory>(mqHost.MessageFactory);
container.Register<ICacheClient>(c =>
(ICacheClient)c.Resolve<IRedisClientsManager>()
.GetCacheClient())
.ReusedWithin(Funq.ReuseScope.None);
I have then created a ControllerBase which for simplicity loads and saves the custom session for each request:
public abstract class ControllerBase : ServiceStackController<CustomUserSession>
{
protected override IAsyncResult BeginExecute (System.Web.Routing.RequestContext requestContext, AsyncCallback callback, object state)
{
ViewBag.Session = this.UserSession;
return base.BeginExecute (requestContext, callback, state);
}
protected override void EndExecute(IAsyncResult asyncResult)
{
SaveSession(null);
base.EndExecute(asyncResult);
}
public void SaveSession(TimeSpan? expiresIn = null)
{
Cache.CacheSet(SessionFeature.GetSessionId(), UserSession, expiresIn ?? new TimeSpan(0, 30, 0));
}
}
I then modify the Hello property to read "World" in one of my actions, and I can clearly see with a breakpoint on the SaveSession method that the value has been properly. However, upon loading the page again and inspecting the loaded session, there's nothing set. Also, looking in the Redis database, the following blob is saved:
{
"createdAt": "/Date(-62135596800000-0000)/",
"lastModified": "/Date(-62135596800000-0000)/",
"providerOAuthAccess": [],
"isAuthenticated": true,
"tag": 0
}
It's not saving my custom property. Can anyone tell me what I'm doing wrong / missing?
=== UPDATE ===
If I change any of the properties from the base AuthUserSession the changes to those properties are persisted - so it would seem that SS somehow decides to disregard the properties from my concrete type.
Because AuthUserSession is a DataContract and attributes are now inherited in v4, you also need to mark each member with [DataMember], e.g:
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string Hello { get; set; }
}
Related
I'm maintaining several applications written in ASP.NET 4.8 (full framework) and ASP.NET Core.
.NET Core implements https://www.w3.org/TR/trace-context/ so I can see my logs (I use Serilog for structured logging) enriched with ParentId, SpanId and most importantly, TraceId.
Is there any way how to read and propagate TraceId also in old ASP.NET 4.8 application?
My applications are doing quite a lot of requests between each other and this would greatly improve debugging experience. Unfortunately most of the requests originate on the old ASP.NET 4.8 apps and go to newer .NET Core ones.
Ideally, I would like to get to the same state as ASP.NET Core apps are - if Request-Id comes from HTTP headers, it is used and filled into ParentId and TraceId and SpanId is generated based on that. Also, it is further propagated to other HTTP requests originating from the .NET Core app.
Thanks!
Ok so I managed to solve it on my own.
First of all, it is needed to add System.Diagnostics.DiagnosticSource nugget package.
Then, create ActivityManager class:
public static class ActivityManager
{
public static void StartActivity()
{
if (Activity.Current == null)
{
var activity = new Activity("Default Activity");
string parentIdFromHeaders = HttpContext.Current?.Request.Headers[GetRequestIdHeaderName()];
if (!string.IsNullOrEmpty(parentIdFromHeaders))
{
activity.SetParentId(parentIdFromHeaders);
}
activity.Start();
Activity.Current = activity;
// Sometimes I had issues with Activity.Current being empty even though I set it
// So just to be sure, I add it also to HttpContext Items.
HttpContext.Current?.Items.Add("Activity", activity);
}
}
public static void StopActivity()
{
GetActivity()?.Stop();
}
public static Activity GetActivity()
{
Activity activity = Activity.Current ?? (Activity)HttpContext.Current.Items["Activity"];
return activity;
}
public static string GetRequestIdHeaderName()
{
return "Request-Id";
}
public static string GetRequestId()
{
Activity activity = GetActivity();
if (activity != null)
{
string activityId = activity.Id;
return activityId;
}
// For the rare cases when something happens and activity is not set
// Try to read Request-Id first, if none, then create new GUID
return HttpContext.Current?.Request.Headers.Get(GetRequestIdHeaderName())
?? Guid.NewGuid().ToString().Replace("-", "");
}
}
and configure Global.asax.cs handler
protected void Application_Start()
{
Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical;
}
protected void Application_BeginRequest()
{
ActivityManager.StartActivity();
}
protected void Application_EndRequest()
{
ActivityManager.StopActivity();
}
Now all incoming requests create new Activity and properly set its ParentId.
To add SpanId, TraceId and ParentId to logging context, I created custom log enricher:
public class TraceLogEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
Activity activity = ActivityManager.GetActivity();
if (activity != null)
{
string parentId = activity.ParentId;
string rootId = activity.RootId;
string activityId = activity.Id;
logEvent.AddPropertyIfAbsent(new LogEventProperty("SpanId", new ScalarValue(activityId)));
logEvent.AddPropertyIfAbsent(new LogEventProperty("ParentId", new ScalarValue(parentId)));
logEvent.AddPropertyIfAbsent(new LogEventProperty("TraceId", new ScalarValue(rootId))); }
}
}
and added it to Serilog configuration with .Enrich.With<TraceLogEnricher>().
Last step is to configure outgoing requests. In HttpClient use DefaultRequestHeaders. (For better convenience, this can be configured also in IHttpClientFactory configuration).
var requestId = ActivityManager.GetRequestId();
client.DefaultRequestHeaders.Add("Request-Id", requestId);
if WebServices are used, it is good idea to add Request-Id header also to web services requests. To do that, override GetWebRequest method of web service client. (These are usually generated as partial class, so override it in your own partial class file).
protected override WebRequest GetWebRequest(Uri uri)
{
var request = base.GetWebRequest(uri);
request.Headers.Add("Request-Id", ActivityManager.GetRequestId());
return request;
}
i´m doing an app, using ServiceStack. I could inject an object without problems, but, the object can be modified outside the Service Class, so, i need to re inject again
Here is the code:
public class ClientManager: ApplicationContext{
public ClientManager(AppConfig appConfig)
{
_appConfig = appConfig;
_activeForm = LayoutFactory(appConfig.Layout);
var appHost = new AppHost(_activeForm, _appConfig);
var listeningOn = string.Format("http://*:{0}/", new Uri(appConfig.UrlBroker).Port);
appHost.Init();
appHost.Start(listeningOn);
var timerMetadata = new Timer(CheckMetadata, null, 0, 60000);
}
}
public class AppHost : AppSelfHostBase
{
private ILayout _layout;
private AppConfig _appConfig;
public AppHost(ILayout activeForm, AppConfig appConfig)
: base("ClientService", typeof(ClientService).Assembly)
{
_layout = activeForm;
_appConfig = appConfig;
}
public override void Configure(Container container)
{
container.Register("activeForm", _layout);
container.Register("config", _appConfig);
}
}
public class ClientService : Service
{
public HttpResult Post(Person request)
{
HttpResult response = new HttpResult();
_initConf = ServiceStackHost.Instance.Container.ResolveNamed<AppConfig>("config");
}
}
So, the class ClientManager has a thread which can modify the object appConfig and activeForm (this objects are injected into the service class)
Now, if i modify the object, it doesn´t inject it again. I think thath i should dispose the AppHost, and start it again, what do you think?
Thanks
It's very rare that you'd want to dispose the AppHost unless you're running Integration tests where you want to start/destroy multiple AppHost instances.
Also I'd strongly recommend against using names when registering dependencies, just use the types of the dependencies as normal, e.g:
container.Register(_layout);
container.Register(_appConfig);
Any dependencies registered in the IOC are then automatically injected into your Service class by declaring a public property with that type, e.g:
public class ClientService : Service
{
public AppConfig AppConfig { get; set; }
public HttpResult Post(Person request)
{
HttpResult response = new HttpResult();
var _initConf = AppConfig;
}
}
This injects the same instance that's registered in the IOC, so if you modify the instance later the Service would inject the same modified instance by default.
I am trying to catch an method in the api that I am implementing. I have created an attribute that is based in this article(http://www.codeproject.com/Articles/682296/Setting-Cache-Control-HTTP-Headers-in-Web-API-Cont).
[Route("")]
[CacheControl(MaxAge = 160)]
public IEnumerable<Club> GetAll()
{
return _clubService.GetAll();
}
The code of this attribute is:
public class CacheControlAttribute : ActionFilterAttribute
{
public int MaxAge { get; set; }
public CacheControlAttribute()
{
MaxAge = 160;
}
public override void OnActionExecuted(HttpActionExecutedContext context)
{
context.Response.Headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(MaxAge)
};
base.OnActionExecuted(context);
}
}
I am calling directly this method from chrome. But the server is always executing the query and I canot get the server to return Not Modified. I am doing something wrong?
---------------------------------------------------------------EDIT--------------------------------------------------------------
These are the header of my call:
Output caching is not currently supported by the Web API, however, someone has already gone to the trouble of building a library that does exactly what you need - AspNetWebApi-OutputCache.
I detected a problem in the RequestFilter execution order.
The ValidationFeature in ServiceStack is a Plugin that just registers a Global Request Filter. The Order of Operations points out that Global Request Filters are executed after Filter Attributes with a Priority <0 and before Filter Attributes with a Priority >=0
My BasicAuth filter has -100 priority, and in fact everything goes well if the Service is annotated at class level, but it fails when the annotation is at method level, with the authentication filter being executed after.
I am using 3.9.70
Is there any quick fix for this? Thanks
When you add the annotation at method level then you are creating an Action Request Filter (because you are adding the annotation to an action method) which in the Order of Operations is operation 8, after the other filters have run.
5: Request Filter Attributes with Priority < 0 gets executed
6: Then any Global Request Filters get executed
7: Followed by Request Filter Attributes with Priority >= 0
8: Action Request Filters (New API only)
The best workaround I can suggest is to reconsider your service structure. I imagine you are having these difficulties because you are adding unauthenticated api methods alongside your secure api methods, and thus are using method level attributes to control authentication. So you are presumably doing something like this Your classes and attributes will be different, this is just exemplar:
public class MyService : Service
{
// Unauthenticated API method
public object Get(GetPublicData request)
{
return {};
}
// Secure API method
[MyBasicAuth] // <- Checks user has permission to run this method
public object Get(GetSecureData request)
{
return {};
}
}
I would do this differently, and separate your insecure and secure methods into 2 services. So I use this:
// Wrap in an outer class, then you can still register AppHost with `typeof(MyService).Assembly`
public partial class MyService
{
public class MyPublicService : Service
{
public object Get(GetPublicData request)
{
return {};
}
}
[MyBasicAuth] // <- Check is now class level, can run as expected before Validation
public class MySecureService : Service
{
public object Get(GetSecureData request)
{
return {};
}
}
}
Solution - Deferred Validation:
You can solve your execution order problem by creating your own custom validation feature, which will allow you to defer the validation process. I have created a fully functional self hosted ServiceStack v3 application that demonstrates this.
Full source code here.
Essentially instead of adding the standard ValidationFeature plugin we implement a slightly modified version:
public class MyValidationFeature : IPlugin
{
static readonly ILog Log = LogManager.GetLogger(typeof(MyValidationFeature));
public void Register(IAppHost appHost)
{
// Registers to use your custom validation filter instead of the standard one.
if(!appHost.RequestFilters.Contains(MyValidationFilters.RequestFilter))
appHost.RequestFilters.Add(MyValidationFilters.RequestFilter);
}
}
public static class MyValidationFilters
{
public static void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
{
// Determine if the Request DTO type has a MyRoleAttribute.
// If it does not, run the validation normally. Otherwise defer doing that, it will happen after MyRoleAttribute.
if(!requestDto.GetType().HasAttribute<MyRoleAttribute>()){
Console.WriteLine("Running Validation");
ValidationFilters.RequestFilter(req, res, requestDto);
return;
}
Console.WriteLine("Deferring Validation until Roles are checked");
}
}
Configure to use our plugin:
// Configure to use our custom Validation Feature (MyValidationFeature)
Plugins.Add(new MyValidationFeature());
Then we need to create our custom attribute. Your attribute will be different of course. The key thing you need to do is call ValidationFilters.RequestFilter(req, res, requestDto); if you are satisfied the user has the required role and meets your conditions.
public class MyRoleAttribute : RequestFilterAttribute
{
readonly string[] _roles;
public MyRoleAttribute(params string[] roles)
{
_roles = roles;
}
#region implemented abstract members of RequestFilterAttribute
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{
Console.WriteLine("Checking for required role");
// Replace with your actual role checking code
var role = req.GetParam("role");
if(role == null || !_roles.Contains(role))
throw HttpError.Unauthorized("You don't have the correct role");
Console.WriteLine("Has required role");
// Perform the deferred validation
Console.WriteLine("Running Validation");
ValidationFilters.RequestFilter(req, res, requestDto);
}
#endregion
}
For this to work we need to apply our custom attribute on the DTO route not the action method. So this will be slightly different to how you are doing it now, but should still be flexible.
[Route("/HaveChristmas", "GET")]
[MyRole("Santa","Rudolph","MrsClaus")] // Notice our custom MyRole attribute.
public class HaveChristmasRequest {}
[Route("/EasterEgg", "GET")]
[MyRole("Easterbunny")]
public class GetEasterEggRequest {}
[Route("/EinsteinsBirthday", "GET")]
public class EinsteinsBirthdayRequest {}
Then your service would look something like this:
public class TestController : Service
{
// Roles: Santa, Rudolph, MrsClaus
public object Get(HaveChristmasRequest request)
{
return new { Presents = "Toy Car, Teddy Bear, Xbox" };
}
// Roles: Easterbunny
public object Get(GetEasterEggRequest request)
{
return new { EasterEgg = "Chocolate" };
}
// No roles required
public object Get(EinsteinsBirthdayRequest request)
{
return new { Birthdate = new DateTime(1879, 3, 14) };
}
}
So when we call the route /EinsteinsBirthday which does not have a MyRole attribute the validation will be called normally, as if using the standard ValidationFeature.
If we call the route /HaveChristmas?role=Santa then our validation plugin will determine that the DTO has our attribute and not run. Then our attribute filter triggers and it will trigger the validation to run. Thus the order is correct.
I am just starting to familiarise myself with ServiceStack and have come upon FluentValidation. I have followed the introductions and created a small Hello App.
My problem is that when I try to validate the request DTO no error messages are returned to describe how it failed validation, only a blank Json object {}.
Myself, I think the validation is autowired to the DTO so there should be no need for me to write any extra code.
The answer is probably blatant but I cannot see it. Any help would be greatly appreciated. My code is below:
namespace SampleHello2
{
[Route("/hello")]
[Route("/hello/{Name}")]
public class Hello
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
public class HelloValidator : AbstractValidator<Hello>
{
public HelloValidator()
{
//Validation rules for all requests
RuleFor(r => r.Name).NotNull().NotEmpty().Equal("Ian").WithErrorCode("ShouldNotBeEmpty");
RuleFor(r => r.Name.Length).GreaterThan(2);
}
}
public class Global : System.Web.HttpApplication
{
public class HelloAppHost : AppHostBase
{
//Tell Service Stack the name of your application and where to find your web services
public HelloAppHost() : base("Hello Web Services", typeof(HelloService).Assembly) { }
public override void Configure(Funq.Container container)
{
//Enable the validation feature
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(HelloValidator).Assembly);
//register any dependencies your services use, e.g:
// container.Register<ICacheClient>(new MemoryCacheClient());
}
}
//Initialize your application singleton
protected void Application_Start(object sender, EventArgs e)
{
new HelloAppHost().Init();
}
}
}
P.S. Really enjoying using ServiceStack, It really is a fantastic project so thanks.
Edit
So for example:
Calling: http://localhost:60063/hello/Ian?format=json returns {"Result":"Hello, Ian"}.
Whereas Calling: http://localhost:60063/hello/I?format=json returns {}.
The second call returns {} where I was expecting auto generated error messages.
I found the answer. It was an overlook on my behalf:
This was in the documentation and I overlooked it:
All Error handling and validation options described below are treated
in the same way - serialized into the ResponseStatus property of your
Response DTO making it possible for your clients applications to
generically treat all Web Service Errors in the same way.
So all that was missing from my code was to add the following line into the HelloResponse class.
public ResponseStatus ResponseStatus { get; set; }