WE used the following post to enable compression on our Service Stack API.
Enable gzip/deflate compression.
We have the following code in my AppHost file:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new ApiServiceRunner<TRequest>(this, actionContext);
}
And In my ApiServiceRunner I have the following:
public override object OnAfterExecute(IRequestContext requestContext, object response)
{
// if it's not null and not already compressed
if ((response != null) && !(response is CompressedResult))
// ToOptimizedResult already picks the most optimal compression (hence the name)
response = requestContext.ToOptimizedResult(response);
return base.OnAfterExecute(requestContext, response);
}
The problem is that this code now runs on EVERY response and we have one endpoint that just calls a json file from the server file system. When the code runs on this json file it totally kills the app pool on the server and I see a stack overflow exception when debugging an integration test that calls this json file.
So we have had to add in the following code into our AppHost file:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
bool useCustomRunner = actionContext.RequestType.Name != "HomepageLayoutConfigRequest";
return useCustomRunner
? new ApiServiceRunner<TRequest>(this, actionContext)
: base.CreateServiceRunner<TRequest>(actionContext);
}
As you can see we don't use our custom ApiServiceRunner when the request type name is HomepageLayoutConfigRequest. This is ugly and we want a better way of doing this.
Any ideas?
thanks
RuSs
ps. here is my latest AppHost CreateServiceRunner override:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
var requestType = actionContext.RequestType;
string message = "The [EnableCompression] attribute exists: {0}";
Debug.WriteLine(string.Format("The requestType was {0}", requestType));
var useCustomRunner = requestType.HasAttribute<EnableCompression>();
Debug.WriteLine(string.Format(message, requestType.HasAttribute<EnableCompression>()));
#region for serviceType if we ever need it. Currently it doesnt work as the guys at SS say it should
// https://stackoverflow.com/questions/19127522/service-stack-enable-compression-globally
// Commented out at there is nothing in the EndpointHost.Metadata so getting a null exception - we only need to use the attribute on the request DTO anyway.
// #Mythz - the following code is the code that doesnt work as per my comments
//var serviceType = EndpointHost.Metadata.GetServiceTypeByRequest(requestType);
// #Mythz- this (serviceType) is always null. It is available in next iteration of debugging (1 iteration behind)
//if (serviceType != null && !useCustomRunner)
//{
// Debug.WriteLine(string.Format("The serviceType was {0}", serviceType));
// useCustomRunner = serviceType.HasAttribute<EnableCompression>();
// Debug.WriteLine(string.Format(message, serviceType.HasAttribute<EnableCompression>()));
//}
#endregion
return useCustomRunner
? new ApiServiceRunner<TRequest>(this, actionContext)
: base.CreateServiceRunner<TRequest>(actionContext);
}
I think you're on the right track, tho I'd prefer to use a Custom Attribute instead, e.g to only enable compression for Service classes or Request DTO's which are marked with [EnableCompression], you can do:
var serviceType = actionContext.ServiceType;
var requestType = actionContext.RequestType;
var useCustomRunner = serviceType.HasAttribute<EnableCompressionAttribute>()
|| requestType.HasAttribute<EnableCompressionAttribute>()
return useCustomRunner
? new ApiServiceRunner<TRequest>(this, actionContext)
: base.CreateServiceRunner<TRequest>(actionContext);
I personally like the declarative intent of [EnableCompression] but you can also use something like [UseCustomRunner] if your ApiServiceRunner ends up doing more than just compression.
Related
I have a .NET core API that performs HTTP connections to other API. I am able to visualize the outgoing HTTP request in Application Insights, under Dependency Event Types, but it has only basic information. I'm looking on how to add more information about the outgoing HTTP call (like the HTTP headers for instance).
I've looked into https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics#trackdependency but I didn't find any concrete way of doing this.
As it has been said, the solution proposed by IvanYang is using the recived request instead of the dependency request.
I've built this ITelemetryInstance for that:
public void Initialize(ITelemetry telemetry)
{
var dependecyTelemetry = telemetry as DependencyTelemetry;
if (dependecyTelemetry == null) return;
if (dependecyTelemetry.TryGetOperationDetail("HttpRequest", out object request)
&& request is HttpRequestMessage httpRequest)
{
foreach (var item in httpRequest.Headers)
{
if (!dependecyTelemetry.Properties.ContainsKey(item.Key))
dependecyTelemetry.Properties.Add(item.Key, string.Join(Environment.NewLine, item.Value));
}
}
if (dependecyTelemetry.TryGetOperationDetail("HttpResponse", out object response)
&& response is HttpResponseMessage httpResponse)
{
var responseBody = httpResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(responseBody))
dependecyTelemetry.Properties.Add("ResponseBody", responseBody);
}
}
This will record all the headers sent to the dependency and also the response received
The other solution given doesn't actually work the way you think it should, since it's attaching the header from the incoming HTTP request to the outgoing dependency request, which is misleading. If you want to attach dependency data to dependency logs then you need to wrap the dependency in a custom dependency wrapper, eg here I'm logging the outgoing payload of the dependency so I can see what's being sent by my system:
Activity activity = null;
IOperationHolder<DependencyTelemetry> requestOperation = null;
if ((request.Method == HttpMethod.Post || request.Method == HttpMethod.Put) && _httpContextAccessor?.HttpContext != null)
{
var bodyContent = await request.Content?.ReadAsStringAsync();
if (!string.IsNullOrEmpty(bodyContent))
{
activity = new Activity("Wrapped POST/PUT operation");
activity.SetTag("RequestBody", bodyContent);
requestOperation = _telemetryClient.StartOperation<DependencyTelemetry>(activity);
}
}
// perform dependency function
httpResponseMessage = await base.SendAsync(request, cancellationToken);
if (activity != null && requestOperation != null)
{
_telemetryClient.StopOperation(requestOperation);
}
I think what you're looking for is ITelemetryInitializer, which can add custom property for dependency telemetry.
And for .net core web project, you can refer to this link.
I write a demo as below:
1.Create a custom ITelemetryInitializer class to collect any dependency data:
public class MyTelemetryInitializer: ITelemetryInitializer
{
IHttpContextAccessor httpContextAccessor;
public MyTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
//only add custom property to dependency type, otherwise just return.
var dependencyTelemetry = telemetry as DependencyTelemetry;
if (dependencyTelemetry == null) return;
if (!dependencyTelemetry.Context.Properties.ContainsKey("custom_dependency_headers_1"))
{
//the comment out code use to check the fields in Headers if you don't know
//var s = httpContextAccessor.HttpContext.Request.Headers;
//foreach (var s2 in s)
//{
// var a1 = s2.Key;
// var a2 = s2.Value;
//}
dependencyTelemetry.Context.Properties["custom_dependency_headers_1"] = httpContextAccessor.HttpContext.Request.Headers["Connection"].ToString();
}
}
}
2.Then in the Startup.cs -> ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
//other code
//add this line of code here
services.AddSingleton<ITelemetryInitializer, MyTelemetryInitializer>();
}
3.Test result, check if the custom property is added to azure portal -> Custom Properties:
I've set attribute routing on a controller class which inherits a base class where I handle I18N culture set/selection logic (as described in article ASP.NET MVC 5 Internationalization) but that process fails, although route seemed to be set correctly.
[RoutePrefix("{culture}")]
public class HomeController : BaseController
{
public ActionResult Index()
{
return View();
}
[Route("Hakkimda")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
When I try to get to link I see grey screen of death on browser with this on address bar:
http://localhost:53530/tr-tr/Hakkimda?MS_DirectRouteMatches=System.Collections.Generic.List%601%5BSystem.Web.Routing.RouteData%5D
I believe the problem is the way base controller implements I18N logic which is based on BeginExecuteCore overloading.
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = RouteData.Values["culture"] as string;
// Attempt to read the culture cookie from Request
if (cultureName == null)
cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; // obtain it from HTTP header AcceptLanguages
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
if (RouteData.Values["culture"] as string != cultureName) {
// Force a valid culture in the URL
RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
Response.RedirectToRoute(RouteData.Values);
}
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
Probably execution precedence of BeginExecuteCore and routing have some mismatch but my knowledge on both don't suffice to solve it.
I've seen this article(What’s New in ASP.NET MVC 5.2 : Attribute routing improvements) but example provided there was a bit different and because it's new there aren't other examples on the net.
mr-anton's answer will stop you getting rubbish in the address bar but It'll also stop the language changing.
I had this issue after a change from MVC5 to MVC5.2
This answer says it is a Microsoft issue
The workaround is to see if the route data is in a nested route key
var routeData = RouteData;
if (routeData.Values.ContainsKey("MS_DirectRouteMatches"))
{
routeData = ((IEnumerable<System.Web.Routing.RouteData>)routeData.Values["MS_DirectRouteMatches"]).First();
}
string cultureName = routeData.Values["culture"] as string;
And then it just works.
remove this code
if (RouteData.Values["culture"] as string != cultureName) {
// Force a valid culture in the URL
RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
Response.RedirectToRoute(RouteData.Values);
}
I searched a lot before putting the questions here but the more I search the more confused I get.
So I have created an handler and I am trying to get the route like this:
public class ExecutionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
{
return base.SendAsync(request, cancellationToken);
}
else
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
}
}
GetRouteData returns null so I can't get to the RouteTemplate property but I can
see the route there in a list deep in the stack. I found so many different ways which one can use to get the route, but those methods evaluate to null as well. I am a bit lost on how to get something so simple done. I am using self host for development but will use IIS for deployment.
UPDATE 1
I forgot to put here what else I had tried:
//NULL
request.GetRouteData();
//EMPTY
request.GetRequestContext().Configuration.Routes.GetRouteData(request).Route.RouteTemplate;
//EMPTY
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;
The route works just fine, but strangely if I try to get the controller to service that request I get a 404... if I just step over that I will get to the controller just fine.
HttpControllerDescriptor httpControllerDescriptor = request.GetRequestContext().Configuration.Services.GetHttpControllerSelector().SelectController(request);
IHttpController httpController = httpControllerDescriptor.CreateController(request);
I am using autofac to discover all the routes which I am defining just like:
[Route("queries/organization/clients")]
[HttpGet]
public ClientInitialScreenModel GetClients()
{
return OrganizationModelsBuilder.GetClientInitialScreen();
}
UPDATE 2
If I GetRouteData gets called after the line above, I am able to get the route template:
base.SendAsync(request, cancellationToken);
var routeData = request.GetRouteData();
So maybe I misunderstood the whole picture and I cant get the route template before the handler that resolves which controller to execute for the request does its work... is that the case?
For reference this is the handler I am working on:
public class ExecutionDelegatingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var securityAuthority = (ISecurityAuthority) request.GetDependencyScope().GetService(typeof (ISecurityAuthority));
var configuration = (IWebApiConfiguration)request.GetDependencyScope().GetService(typeof(IWebApiConfiguration));
var tsc = new TaskCompletionSource<HttpResponseMessage>();
var httpResponseMessage = new HttpResponseMessage();
if (request.RequestUri.AbsolutePath.Equals(configuration.CommandGatewayUrl, StringComparison.InvariantCultureIgnoreCase))
{
var apiMessage = JsonConvert.DeserializeObject<ApiCommandEnvelope>(request.Content.ReadAsStringAsync().Result);
if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(apiMessage, request.Headers))
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
else
{
var messageProcessor = (IWebApiMessageProcessor)request.GetDependencyScope().GetService(typeof(IWebApiMessageProcessor));
var reponse = messageProcessor.HandleRequest(apiMessage);
httpResponseMessage.StatusCode = (HttpStatusCode) reponse.StatusCode;
if (!string.IsNullOrEmpty(reponse.Content))
{
httpResponseMessage.Content = new StringContent(reponse.Content);
}
}
}
else
{
if (securityAuthority != null && !securityAuthority.VerifyPermissionToExecute(request.GetRouteData().Route.RouteTemplate, request.Headers))
{
httpResponseMessage.StatusCode = HttpStatusCode.Unauthorized;
}
else
{
return base.SendAsync(request, cancellationToken);
}
}
tsc.SetResult(httpResponseMessage);
return tsc.Task;
}
UPDATE 3
The code runs fine in a non self hosting environment, so this is more like a self host issue.
The Web Api still has a lot to improve. It was tricky to find a way to get this working and I just hope this saves other guys from spending all the time I did.
var routeTemplate = ((IHttpRouteData[]) request.GetConfiguration().Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate;
I had a similar issue, but was able to get the route inside the message handler by the following:
request.GetConfiguration().Routes.GetRouteData(request).Route.RouteTemplate;
The answer from Marco (shown below) is correct so long as there isn't more than one route defined with the same HttpMethod. The .First() will grab the 1st route defined in that specific ApiController, but this doesn't ensure it grabs the correct one. If you use the ControllerContext to get the Route, you can be sure you've got the exact endpoint you want.
Marco's:
var routeTemplate = ((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate;
The code:
((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request).Values["MS_SubRoutes"])
actually returns a collection of IHttpRouteData, and it contains a record for each endpoint which has the same HttpMethod (Post, Get, etc)... The .First() doesn't guarantee you get the one you want.
Guaranteed To Grab Correct Endpoint's RouteTemplate:
public static string GetRouteTemplate(this HttpActionContext actionContext)
{
return actionContext.ControllerContext.RouteData.Route.RouteTemplate;
}
I used an extension method so to call this you'd do:
var routeTemplate = actionContext.GetRouteTemplate();
This will assure that you get the specific RouteTemplate from the endpoint making the call.
I think you can get route Data from request.Properties property and easy to unit test.
/// <summary>
/// Gets the <see cref="System.Web.Http.Routing.IHttpRouteData"/> for the given request or null if not available.
/// </summary>
/// <param name="request">The HTTP request.</param>
/// <returns>The <see cref="System.Web.Http.Routing.IHttpRouteData"/> or null.</returns>
public static IHttpRouteData GetRouteData(this HttpRequestMessage request)
{
if (request == null)
{`enter code here`
throw Error.ArgumentNull("request");
}
return request.GetProperty<IHttpRouteData>(HttpPropertyKeys.HttpRouteDataKey);
}
private static T GetProperty<T>(this HttpRequestMessage request, string key)
{
T value;
request.Properties.TryGetValue(key, out value);
return value;
}
Reference link of code
I'm trying to create a simple Credentials Auth using OrmLiteAuthRepository(Postgres) and Memcached as caching layer on Mono 3.2.x / Ubuntu 12.04 in an MVC Application - I am using ServiceStack libraries version 4.0x
I am using a custom session object, adapted from ServiceStack's SocialBootstrap example
What works perfectly:
Getting the session inside a controller action, such as:
var currentSession = base.SessionAs<MyCustomUserSession>();
However, I don't want to check / validate the session and what may or may not be inside it in the action code, I would like to use an attribute, and this leads me to:
What does not work: Using the Authenticate attribute above the action name:
My problem (null AuthSession) shows up when trying to utilize the [Authenticate] attribute on an MVC action.
[Authenticate]
public ActionResult Index()
{
return View();
}
I have managed to narrow it down to the fact that ExecuteServiceStackFiltersAttribute executes this code, but it appears the AuthSession has not yet been made available by the SessionFeature - so the AuthSession will always be null at this point:
var authAttrs = GetActionAndControllerAttributes<AuthenticateAttribute>(filterContext);
if (authAttrs.Count > 0 && ( ssController.AuthSession==null || !ssController.AuthSession.IsAuthenticated))
{
filterContext.Result = ssController.AuthenticationErrorResult;
return;
}
If, for example I override the AuthenticationErrorResult and try to throw an exception if I manually initialize the session from the SessionFeature, it will throw the "there is life in the session" exception (of course, when I logged in with a valid user):
public override ActionResult AuthenticationErrorResult
{
get
{
if (AuthSession == null)
{
// the Authenticate filter is triggered by ExecuteServiceStackFilters attribute
// which seems to always have AuthSession null
var session = SessionFeature.GetOrCreateSession<MyCustomUserSession>(AuthService.Cache);
if (session == null || (session != null && session.IsAuthenticated == false))
{
throw new Exception("Hmmm...dead as a dodo");
}
else
{
throw new Exception("there is life in the session:" + session.UserName);
}
}
var returnUrl = HttpContext.Request.Url.PathAndQuery;
return new RedirectResult(LoginRedirectUrl.Fmt(HttpUtility.UrlEncode(returnUrl)));
}
}
Aside from creating my custom attributes / filters, is there a solution I should try (properties to set) with the incumbent ServiceStack codebase? If I'm missing something, please let me know.
My regards for a great project in any case.
My problem (null AuthSession) shows up when trying to utilize the [Authenticate] attribute on an MVC action.
Are you getting an Exception or are you just getting redirected to the 'Login' page? If you are not getting an Exception and just be redirected because you're not authenticated, the below may work. Also, are you implementing your own Custom Authentication Provider? If so, could you post a sample of it?
I don't think you have it in your code samples but I think your MVC Controller code is probably something like...
public class SomeController : ServiceStackController
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}
Can you try adding your custom MyCustomUserSession to the Type of the ServiceStackController making it...
public class SomeController : ServiceStackController<MyCustomUserSession>
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}
I'm working on an API where I'd like to be able to customize the response structure based on a parameter from the client. Response filters seem like a good place to do this in order to avoid doing so in each service or action. The problem is that while I have access to the response DTO returned by the action, and could change its properties, I can't find how or where to replace the object entirely.
Naively replacing the object in the response filter did not work, but this help illustrate what I'm trying to do:
public class ChangeResponseAttribute : ResponseFilterAttribute
{
public override void Execute(IHttpRequest req, IHttpResponse res, object responseDto)
{
var overrideText = req.QueryString["override"];
if (!String.IsNullOrEmpty(overrideText))
responseDto = new { Message = overrideText };
}
}
[ChangeResponse]
public class TodosService : Service
{
public object Get(Todos request)
{
return new object[0];
}
}
It looks like another option would be to write the custom response directly & end the request, but that would bypass any other processing left to do by ServiceStack.
Is there a better place to do this than a response filter? Or do I need to bite the bullet and return the optimal DTO in each action?
You can't change the Response DTO in a filter, but yes one option is to write the response in the filter itself (see this answer for an example of how to do this).
The other option is to use a ServiceRunner and override the OnAfterExecute() custom hook which does let you modify the response returned, e.g:
public class MyServiceRunner<T> : ServiceRunner<T>
{
public override object OnAfterExecute(
IRequestContext requestContext, object response)
{
// Called just after any Action is executed
var overrideText = req.Get<IHttpRequest>().QueryString["override"];
return !string.IsNullOrEmpty(overrideText)
? new { Message = overrideText } : null;
}
}
To get ServiceStack to use it you need to override the CreateServiceRunner method in your AppHost, e.g:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(
ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext);
}