All,
I am trying to modify the payload of incoming object via the web API. Currently I'm using a custom formatter which inherits from JsonMediaTypeFormatter and overrides the relevant methods.
Looks like this:
public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger,
CancellationToken cancellationToken)
{
object obj = await base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);
TrySetEventNo(obj, GetEventNo());
return obj;
}
private void TrySetEventNo(object content, long eventNo)
{
if (content is EventModelBase)
{
EventModelBase eventBase = (EventModelBase)content;
eventBase.EventNo = eventNo;
}
}
I'm using this to track every object that comes through the API.
Before all of this happens, I have a MessageHandler which is creating an event number and adding it to Request.Properties.
Trying to get the event number in the formatter which was created previously in the MessageHandler is proving difficult. Access HttpContext.Current.Items["MS_HttpRequestMessage"].Properties seems to be a different request as it does not contain the event number.
I've two questions:
Am I doing this the correctly or is there a better way?
If I am taking the correct approach, how to I get the correct Request to extract the event number?
Thanks
I've found a solution, instead of attempting to do this inside a formatter I'm now using an ActionFilterAttribute.
overriding OnActionExecuting(HttpActionContext actionContext) and enumerating action actionContext.ActionArguments.
The complete solution looks like so:
public class SetModelEventNumberActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
foreach (KeyValuePair<string, object> actionArgument in actionContext.ActionArguments)
{
TrySetEventNo(actionArgument.Value, GetEventNo(actionContext));
}
base.OnActionExecuting(actionContext);
}
private void TrySetEventNo(object content, long eventNo)
{
if (content is EventPivotRequestMessageBase)
{
EventPivotRequestMessageBase eventBase = (EventPivotRequestMessageBase)content;
eventBase.EventNo = eventNo;
}
}
private long GetEventNo(HttpActionContext actionContext)
{
long eventNo = (long)actionContext.Request.Properties[Constant.EVENT_ID];
return eventNo;
}
}
Related
I'm trying to log all outbound requests that go to service references, including the full request and response body. I thought I had a solution using behaviorExtensions but, after deploying, it became clear that the extension was shared between multiple requests.
Here's my current code:
public class LoggingBehaviorExtender : BehaviorExtensionElement
{
public override Type BehaviorType => typeof(LoggingRequestExtender);
protected override object CreateBehavior() { return new LoggingRequestExtender(); }
}
public class LoggingRequestExtender : IClientMessageInspector, IEndpointBehavior
{
public string Request { get; private set; }
public string Response { get; private set; }
#region IClientMessageInspector
public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
Request = request.ToString();
Response = null;
return null;
}
public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
Response = reply.ToString();
}
#endregion
#region IEndpointBehavior
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
#endregion
}
Then, when I reach the point to log, I extract the behavior...
var lre = client.Endpoint.Behaviors.OfType<LoggingRequestExtender>().FirstOrDefault();
var req = lre?.Request;
var resp = lre?.Response;
Adding debugging logging to the LoggingRequestExtender, I found it was only instantiated once for multiple requests.
Is there a way to make sure this behavior class is instantiated fresh for each thread? Or is there a better way of getting the full request / response body when making service calls?
Edit / Partial answer:
Since writing this I have discovered that the value returned by BeforeSendRequest is passed into AfterReceiveReply as the correlationState so I can connect the request and response using a guid:
public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
var guid = Guid.NewGuid();
WebServiceLog.LogCallStart(guid, channel.RemoteAddress.ToString(), request.ToString());
return guid;
}
public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
Guid guid = (Guid)correlationState;
WebServiceLog.LogCallEnd(guid, reply.ToString());
}
I see two flaws to this approach. One, which is livable, is that this requires a log insert and then update rather than a single insert.
The second is more of an issue: In the case of an exception (e.g. timeout), we never hit AfterRecieveSupply so the log doesn't know what happened. I can separately log the exception...
try
{
response = client.SomeFunction(request);
}
catch (Exception ex)
{
AppLog.Error("Some function failed", ex);
}
... but I can't see a way of accessing the guid outside of BeforeSendRequest / AfterReceiveReply so I have nothing to tie the exception log to the service request log.
There are several approaches to this.
1, The situation you have described with having to log calls separately doesn't have to be like that. If your WCF service is in a non load balanced server just add the request to a MemoryCache using the Guid as a key. When the request comes in then pull off the request and log in one go. To capture the timed out calls you could run a process on a thread that would check the MemoryCache every x minutes to pull out and log (using an adequate lock to ensure thread saftey).
If the WCF service is in a load balanced environment then all you do is the same as above but store to a no sql type data store.
2, Is the code that makes the outbound calls within your scope for change? If so, you can forgo creating a behavior extension and create a bespoke message logger instead. Using a class that implements IDisposable you can write nice code like this..
RequestMessage request = new RequestMessage();
ResponseMessage response = null;
using (_messageLogger.LogMessage(request, () => response, CallContextHelper.GetContextId(), enabled))
{
response = _outboundService.DoSomething(request);
}
This will then not need another process to capture any timed out threads which will be handled in the dispose method.
If you need more clarity then let me know, hopefully this helps you...
Background:
I want to authenticate a POST request to my web API using an implementation of IAuthenticationFilter injected using Ninject. To authenticate a request I need access to request body.
Problem:
ActionContext.ActionArguments, which I usually use to access request payload, is empty when I try to access it inside the filter.
Question:
How to access POST request payload inside an IAuthenticationFilter implementation?
Why ActionContext.ActionArguments is empty inside an IAuthenticationFilter implementation, but has values if my filter implements ActionFilterAttribute?
Code:
Filter implementation:
public class AuthenticateFilter : IAuthenticationFilter
{
private const string AuthenticationHeader = "X-Auth-Token";
private const string UserHeader = "X-Auth-User";
private readonly ILog log;
public AuthenticateFilter(ILog log)
{
this.log = log;
}
public Task AuthenticateAsync(HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
// context.ActionContext.ActionArguments is empty
if (!IsAuthenticated(context))
{
context.ErrorResult =
new StatusCodeResult(HttpStatusCode.Unauthorized,
context.Request);
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken)
{
context.Result =
new StatusCodeResult(HttpStatusCode.Unauthorized,
context.Request);
return Task.FromResult(0);
}
private bool IsAuthenticated(HttpAuthenticationContext context)
{
// Authentication code here
// context.ActionContext.ActionArguments is empty
}
}
The filter is injected using Ninject when controller method has a attribute.
kernel.BindHttpFilter<AuthenticateFilter>(FilterScope.Action)
.WhenActionMethodHas<AuthenticateAttribute>();
AuthenticateAttribute is an empty ActionFilterAttribute.
public class AuthenticateAttribute : ActionFilterAttribute
{
}
Thank you!
This is expected behavior. Authentication and Authorization filters run before ModelBinding/Formatter deserialization stage, where as Action filters run after this stage.
I struggled a bit myself with the same situation, in case it helps anyone, you need to use Reflection and System.Web.Helpers's Json.Decode:
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
var content = request.Content.ReadAsAsync(typeof(Object)).Result.ToString();
var methodInfo = ((ReflectedHttpActionDescriptor)request.Properties["MS_HttpActionDescriptor"]).MethodInfo; // get the method descriptor
if (methodInfo.GetParameters().Any()) //this will get the parameter types
{
var parameterType = methodInfo.GetParameters().First().ParameterType; //you iterate can through the parameters if you need
var casted = Json.Decode(content, parameterType); //convert the json content into the previous type (your parameter)
//do something with your populated object :)
}
return Task.FromResult(context.Request);
}
I am working on a (self-hosted) WebApi application using Visual Studio 2012 targetting .Net 4+ and with an eye on moving to VS2013 as soon as possible to take advantage of MVC 5 and WebApi 2.0.
I need to prefix all outgoing Uri's with a string that is sent as a query parameter on the incoming request. The challenge is to do this without any specific code in controllers, models or views/viewmodels. And if at all possible, I would also like to stay away from using an action filter that would need to use reflection/recursion to work through all properties of the outgoing response, though it would be fine to use a result action filter to make the url prefix available to the serializer.
What I have come with so far is a DelegatingHandler to get the url prefix to use from the request's query parameters so it can be added as a property to some request/controller context object; and with a JsonConverter to add the desired prefix to all Uri type properties in responses.
What I am left with is getting the Url prefix specified in the request's parameters to the convertor. The serializer that is passed to the JsonConvertor does have a Context property, but I can't see if and how that is related to the request context.
I can think of a number of approaches to solve this, but keep run into "lack of knowledge" walls on how MVC/WebApi carries request (context) information around the pipeline.
Delegating Handler:
class UrlPrefixHandler : DelegatingHandler
{
private string GetUrlPrefixValue(HttpRequestMessage request)
{
var queryStrings = request.GetQueryNameValuePairs();
if (queryStrings == null)
return null;
var match = queryStrings.FirstOrDefault(kv => string.Compare(kv.Key, "url_prefix", true) == 0);
if (string.IsNullOrEmpty(match.Value))
return null;
return match.Value;
}
async protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
string urlPrefix = GetUrlPrefixValue(request);
// TODO : How do I get this to the serializer on a per request basis?
// and do this without placing requirements on controllers/models/views?
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}
Json Converter:
class UriPrefixConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Uri).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject result = new JObject();
//serializer.
Uri u = (Uri)value as Uri;
// TODO : Getting the prefix from the request's context somehow
Uri prefixed = new Uri("/uriPrefix" + u.OriginalString, UriKind.Relative);
writer.WriteValue(prefixed.OriginalString);
}
Using C# I'd like to take control over the reading of the HTTP Requests from a POST. Primarily to read the stream of a multipart/form-data file upload to track the stream as it's received from the client.
Using the ProcessRequest or the Async BeginProcessRequest the body has already been parsed by ASP.net / IIS.
Is there a way to override the built-in reading via a HTTPHandler, or will I have to use another mechanism?
Many thanks
Andy
Update - Added code example as requested, albeit no different to a normal class that's implemented IHttpHandler
public class MyHandler : IHttpHandler
{
public bool IsReusable { get { return true; } }
public void ProcessRequest(HttpContext context)
{
// The body has already been received by the server
// at this point.
// I need a way to access the stream being passed
// from the Client directly, before the client starts
// to actually send the Body of the Request.
}
}
It appears that you can capture the stream via the context.BeginRequest event of a HttpModule.
For example :
public class Test : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(onBeginRequest);
}
public void onBeginRequest(object sender, EventArgs e)
{
HttpContext context = (sender as HttpApplication).Context;
if( context == nul ) { return; }
if (context.Request.RawUrl.Contains("test-handler.ext"))
{
Logger.SysLog("onBeginRequest");
TestRead(context);
}
}
// Read the stream
private static void TestRead(HttpContext context)
{
using (StreamReader reader = new StreamReader(context.Request.GetBufferlessInputStream()))
{
Logger.SysLog("Start Read");
reader.ReadToEnd();
Logger.SysLog("Read Completed");
}
}
}
Really I was trying to avoid HttpModules, as they are processed for every .net request, so really I'd stil like to know how to do it via a HTTPHandler.
You can definitely do that by implementing an IHttpHandler.
This example will get you started. There is no need to override the built-in reading.
You receive all the data in the Request, and can process it as required.
I am writing a media type formatter for HTML to automatically generate a Razor view based on an html request from the user. I am doing this for use inside a SelfHosted service. I need to detect what controller/action was requested to allow me to pick the view to render into.
public class RazorHtmlMediaTypeFormatter : MediaTypeFormatter
{
public RazorHtmlMediaTypeFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
public override bool CanWriteType(Type type)
{
return true;
}
public override bool CanReadType(Type type)
{
return false;
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, System.Net.TransportContext transportContext)
{
return Task.Factory.StartNew(() =>
{
var view = Razor.Resolve(String.Format("{0}.{1}.cshtml", something.Controller, something.Action), value);
byte[] buf = System.Text.Encoding.Default.GetBytes(view.Run(new ExecuteContext()));
stream.Write(buf, 0, buf.Length);
stream.Flush();
});
}
}
Why not wrapping your returned objects in Metadata<T>?
I.e. return, instead of MyCustomObject, Metadata<MyCustomObject>. As Metadata properties, you can set controller name and action. Then in the formatter, just decouple the Metadata and your custom object, and serialize just that custom object.
I blogged about this approach here - http://www.strathweb.com/2012/06/extending-your-asp-net-web-api-responses-with-useful-metadata/. While the purpose of the article is a bit different, I am sure you can relate it to your needs.
Edit: or if you are OK with a small hack, use a custom filter and headers:
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Response.Headers.Add("controller", actionContext.ActionDescriptor.ControllerDescriptor.ControllerName);
actionContext.Response.Headers.Add("action", actionContext.ActionDescriptor.ActionName;);
base.OnActionExecuting(actionContext);
}
then just read it from the headers in the formatter, and remove the header entries so that they don't get sent to the client.
Web API Contrib has a working RazorViewFormatter in here.