In a WebAPI Formatter, how can you get the URL from HttpContent? - asp.net-web-api

In a WebAPI service, we are using a Formatter to read a content parameter on a request. We need access to the URL in order to transform the content parameter correctly. HttpRequestMessage isn't available, and we can't use HttpContext.Current.Request because HttpContext.Current is null. Accessing the HttpRequestMessage on a Read was requested at http://aspnetwebstack.codeplex.com/workitem/82, but this issue was closed because HttpContent is available on a Read. However, I don't know how to get the URL from HttpContent, or even if it's possible.

There is a method called GetPerRequestFormatterInstance on the formatter which you can override to create a new instance of the formatter with the stateful information about the request in it. By the way, this method GetPerRequestFormatterInstance is only called during the request's deserialization stage. Example below:
public class TextPlainFormatter : BufferedMediaTypeFormatter
{
public TextPlainFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
}
public HttpRequestMessage CurrentRequest
{
get;
private set;
}
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
TextPlainFormatter frmtr = new TextPlainFormatter();
frmtr.CurrentRequest = request;
//Copy from the original formatter instance to the new instance
frmtr.MediaTypeMappings.Clear();
foreach (MediaTypeMapping mediaTypeMapping in this.MediaTypeMappings)
{
frmtr.MediaTypeMappings.Add(mediaTypeMapping);
}
frmtr.RequiredMemberSelector = this.RequiredMemberSelector;
frmtr.SupportedEncodings.Clear();
foreach (Encoding supportedEncoding in this.SupportedEncodings)
{
frmtr.SupportedEncodings.Add(supportedEncoding);
}
frmtr.SupportedMediaTypes.Clear();
foreach (MediaTypeHeaderValue supportedMediaType in this.SupportedMediaTypes)
{
frmtr.SupportedMediaTypes.Add(supportedMediaType);
}
return frmtr;
}

Related

Web API Validation for Model Bound in GET request

I have created a custom Model Binder to read the data from the URI in a specific format
public ResponseObject Get([FromUri(BinderType = typeof(CustomModelBinder)]ProductFilter product
{...}
public class ProductFilter
{
[Required(ErrorMessage = #"Name is required")]
public string Name { get; set; }
}
public class CustomModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//Code to convert the uri parameters to object
return true;
}
}
In the above example, i need the name to be passed from the client before executing the Action.
But, I am unable to run the in-built validations on the Product class using this?
Any ideas?
I wrote in a custom action filter and I registered this action filter in the GlobalConfiguration for all the services. The action filter hooks on to onActionExecuting, looks for the validation in the bound arguments.
bool isValid;
foreach (var item in actionContext.ActionArguments)
{
var parameterValue = item.Value;
var innerContext = new ValidationContext(parameterValue);
if(parameterValue != null)
{
var innerContext = new ValidationContext(parameterValue);
isValid = Validator.TryValidateObject(parameterValue, innerContext, results, true);
}
}
//If not valid, throw a HttpResponseException
if(!isValid)
throw new HttpResponseException(HttpStatusCode.BadRequest);
else
base.onActionExecuting(actionContext);
With more tuning, the exact validation message can be retrieved from the validation context and sent as the response message.
I was also able to extend this to having validation attributes on the parameters themselves, thereby giving more flexibility to my Api

How to get HttpRequestMessage instead of HttpContext.Current in WebApi

I have found several sources that say that you should not use HttpContext.Current in WebApi but none that say how you should handle those cases where we used to use HttpContext.Current.
For example, I have a LinkProvider class that creates links for an object. (simplified to stay on topic).
public abstract class LinkProvider<T> : ILinkProvider<T>
{
protected ILink CreateLink(string linkRelation, string routeName, RouteValueDictionary routeValues)
{
var context = System.Web.HttpContext.Current.Request.RequestContext;
var urlHelper = new System.Web.Mvc.UrlHelper(context);
var url = string.Format("{0}{1}", context.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority), urlHelper.RouteUrl(routeName, routeValues));
///...
return new Link(linkRelation, url);
}
}
and this class is used by a MediaTypeFormatter.
This class is expected to build a link using the same host that came from the original request and leveraging any route values that were on the original request.
But... how do I get a hold of the HttpRequestMessage? This will be encapsulated by a MediaTypeFormatter - but it doesn't have one either.
There must be an easy way to get hold of the HttpRequestMessage - what am I overlooking?
thanks
Jon
I ended up creating the following base Formatter which exposes the request, now I will be able to pass it along to the LinkProvider.
public class JsonMediaTypeFormatterBase : JsonMediaTypeFormatter
{
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, System.Net.Http.HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
Request = request;
return base.GetPerRequestFormatterInstance(type, request, mediaType);
}
protected HttpRequestMessage Request
{
get;
set;
}
}

Appropriate pattern for setting request object properties from POST request with MVC3?

With incoming POST requests to my MVC3 application, I want to validate the incoming request parameters. If an invalid parameter exists, an exception is thrown.
Given the following object:
public class ActionRequest
{
public string ActionRequestPassword { get; set; }
public bool EnableNewsfeedAppPool { get; set; }
}
With incoming post requests, I want to initialize the object with the appropriate properties via:
public class NewsfeedAppPoolController : Controller
{
[ActionName("EnableAppPool"), AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[NoCache]
public ActionResult EnableAppPool(FormCollection formCollection)
{
Models.ActionRequest actionRequest = ValidatePOSTRequest(formCollection);
// do things with actionRequest
return null;
}
private Models.ActionRequest ValidatePOSTRequest(FormCollection formCollection)
{
try
{
Type actionRequestType = typeof(Models.ActionRequest);
System.Reflection.PropertyInfo propertyInfo = null;
object systemActivatorObject = Activator.CreateInstance(actionRequestType);
foreach (var key in formCollection.AllKeys)
{
propertyInfo = typeof(Models.ActionRequest).GetProperty(key);
Type t = propertyInfo.PropertyType; // t will be System.String
if (t.Name == "Int32")
{
actionRequestType.GetProperty(key).SetValue(systemActivatorObject, Convert.ToInt32(formCollection[key]), null);
}
else
{
actionRequestType.GetProperty(key).SetValue(systemActivatorObject, formCollection[key], null);
}
}
return (Models.ActionRequest)systemActivatorObject;
}
catch (Exception ex)
{
throw ex;
}
}
}
I would like to know if there can be any improvements made to this, or recommendations of how else to accomplish this in an efficient manner.
Thanks.
ASP.Net MVC already does all of this for you.
Just add a Models.ActionRequest actionRequest parameter to your action.
If you want to add additional validation logic, use System.ComponentModel.DataAnnotations.
Simply use the default model binder which will take care of instantiating and binding the ActionRequest from the request parameters:
public class NewsfeedAppPoolController : Controller
{
[ActionName("EnableAppPool"), AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[NoCache]
public ActionResult EnableAppPool(ActionRequest actionRequest)
{
// do things with actionRequest
return null;
}
}
The appropriate pattern is,
[HttpPost]
public ActionResult Save(Employee employee)
{
if(ModelState.IsValid)
{
db.Save(employee);
RedirectToAction("Index");
}
return View();
}
Notes:
The employee instance is automatically created and populated by the default model binder from the values available in the request(form, querystrings, routedata and more)
When the default model binder binds the values to the model it also does the validation and store all the errors in the ModelState dictionary, so by checking the ModelState.IsValid you can know that whether the validation is succeeded or not.
To know more about model binding refer this.
To know more about model validation refer this.

How to return HTTP status code form Custom Model Binder

I have a custom model binder which pulls an implementation of an interface from a MEF container. It is implemented as follows:
public class PetViewModelBinder : DefaultModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var petId = bindingContext.ValueProvider.GetValue("id");
var container = controllerContext.HttpContext.Application[MvcApplication.PLUGINS] as CompositionContainer;
Lazy<IPet, IPetMetadata> pet = null;
try
{
pet = container.GetExport(typeof(IPet), petId);
var petVM = new Models.PetViewModel(pet);
bindingContext.ModelMetadata.Model = petVM;
return base.BindModel(controllerContext, bindingContext);
}
catch (Exception)
{
throw;
}
finally
{
container.ReleaseExport(pet);
}
}
This works splendidly when MEF has an Export of petId... but returns http status 500 (server error) when an Export does not exist. Error message obfuscation requirements dictate http status 403 (forbidden) should be returned.
What can be done to trap the error, change the response status, and either not return content, or re-route the Action to handle this condition?
If you want to return a particular http status code you should do that from a controller or action filter.
One way to do this is to return null from your model binder and handle that in your controller. This is a bit coarse however so you won't be able to distinguish between different errors.
Another way to do it would be to throw a specific exception and handle that in your (global) error handling. A customized HandleError action filter could do this:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public int StatusCode { get; set; }
public override void OnException( ExceptionContext filterContext )
{
base.OnException( filterContext );
if ( StatusCode > 0 )
{
filterContext.HttpContext.Response.StatusCode = StatusCode;
}
}
}
In your controller, decorate the action with this attribute:
[CustomHandleError( ExceptionType = typeof (NotAllowedException), View = "~/Views/Shared/Error.cshtml",
StatusCode = 403 )]
public ActionResult Index( FancyModel model )
{
return View( model );
}
Finally, in your model binder throw a NotAllowedException, which is a custom exception type you'll also need to define.
Note that this will only work on your development setup if you have enabled custom errors in your web.config file.

Getting the HttpActionExecutedContext Result values

I have created a filter which inherits the System.Web.Http.Filters.ActionFilterAttribute in the asp.net web api and would like to access some of the data inside the HttpActionExecutedContext result object.
At what stage/when does this object get populated? As I looked at it when overriding the OnActionExecuted method and its always null?
Any ideas?
Edit:
for example here in my custom filter:
public override OnActionExecuted(HttpActionExecutedContext context)
{
//context.Result.Content is always null
base.OnActionExecuted(context);
}
Use this function to get body of request in web api
private string GetBodyFromRequest(HttpActionExecutedContext context)
{
string data;
using (var stream = context.Request.Content.ReadAsStreamAsync().Result)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
data = context.Request.Content.ReadAsStringAsync().Result;
}
return data;
}
Ended up using ReadAsStringAsync on the content result.
I was trying to access the property before the actual request had finished.
While the awarded answer referred to ReadAsStringAsync, the answer had no example. I followed the advice from gdp and derived a somewhat working example...
I created a single class called MessageInterceptor. I did nothing more than derive from ActionFilterAttribute and it immediately started to intercept webAPI method calls prior to the controller getting it, and after the controller finished. Here is my final class. This example uses the XML Serializer to get both the request and response into an XML string. This example finds the request and response as populated objects, this means deserialization has already occurred. Collecting the data from a populated model and serializing into an XML string is a representation of the request and response - not the actual post request and response sent back by IIS.
Code example - MessageInterceptor
using System.IO;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Xml.Serialization;
namespace webapi_test
{
public class MessageInterceptor : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
var headers = actionContext.Request.Content.Headers.ToString();
var request = actionContext.ActionArguments.FirstOrDefault().Value;
var xml = SerializeXMLSerializer(request, "");
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
var headers = actionExecutedContext.Response.Content.Headers.ToString();
var response = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
var xml = SerializeXMLSerializer(response, "");
}
public static string SerializeXMLSerializer(object o, string nameSpace)
{
string serializedValue;
var writer = new StringWriter();
XmlSerializer serializer = new XmlSerializer(o.GetType(), nameSpace);
serializer.Serialize(writer, o);
serializedValue = writer.ToString();
return serializedValue;
}
}
}
Use below to read Response string:
public static string GetResponseContent(HttpResponseMessage Response)
{
string rawResponse = string.Empty;
try
{
using (var stream = new StreamReader(Response.Content.ReadAsStreamAsync().Result))
{
stream.BaseStream.Position = 0;
rawResponse = stream.ReadToEnd();
}
}
catch (Exception ex) { throw; }
return rawResponse;
}

Resources