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;
}
}
Related
I am spending a lot of effort debugging ajax calls. The common issues are
number of parameters dont match from the ajax to the controller
spelling of the #RequestMapping parameter does not match
If the type does not match the call happens and it can be debugged. But the bigger issue is the above two. I have 88 parameters that I am passing and have a hell of a time trying to figure out what is missing or spelt incorrectly.
example
#RequestMapping("/saveClient")
public #ResponseBody String saveClientAJAXMethodView(#RequestParam(value = "clientName") String clientName,
.... 88 parameters more
$
.ajax({
type : "Post",
url : "saveClient",
data : {
clientName : clientName,
... 88 parameters more
I got this error
The request sent by the client was syntactically incorrect.
So I changed the signature of my controller to add , method = RequestMethod.POST).
Now I am getting
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource.
Its clearly a "POST" and still it get a request method GET not supported.
The question is NOT how to solve this problem. The question is how to debug such issues easily. What errors map to what issues, how to debug the 88 parameter spellings and count ? There must be a easier way to do this debugging.
I use the following
Debugging Mode of the controller
Inspect on Chrome.
Since you are sending huge amount of parameter in URL for POST request. I would suggest you to send your data in body.
For example if you are sending parameters like clientId, clientName, clientEmail etc.. you have used #RequestParam annotation to get individual parameter data in your controller:
String saveClientAJAXMethodView( #RequestParam String clientId,
#RequestParam String clientName,
#RequestParam String clientEmail
.... more parameters)
Instead of using #RequestParam I would suggest you to use #RequestBody, For this you need to create a Data transfer object (DTO) like this:
class ClientInfo{
String clientId,
String clientName,
String clientEmail,
....
.... other variables
.... getters and setters of variables
}
And then use this DTO in your controller method like this:
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
}
Using this approach you will not get any exception regarding spelling mistake or parameter missing .The value will be assigned to a DTO variable if you are sending value with right key as specified in DTO.
To count variables in ClientInfo object you will need to cast ClientInfo to JSONObject and use its size() method to get count of variables
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
JSONObject json = new JSONObject(clientInfo);
System.out.println(json.keySet().size());
}
Your ajax call will look like this:
var clientInfo = {
'clientName': 'tom',
'clientId': '23AZ1',
'clientEmail': 'xyz#gmail.com',
...
};
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(clientInfo),
contentType: "application/json",
complete: callback
});
I hope following steps would help you debug:
1- Use a filter to intercept request.
2- Create a custom annotation which would indicate that you want to debug this method.
3- Use the method defined in this post Can I get all of requestMapping URL with GET method in the Spring? and your custom annotation to store list of all methods which you want to debug in a singleton bean.
4- Now write some logic in filter which would print mismatch between the method parameters and request parameters.
CustomFilter:
public class CustomFilter extends GenericFilterBean {
#Autowired
#Qualifier("printMismatchMethods")
HashMap<String,Method> methodsToCheck;
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
HttpServletRequest r = (HttpServletRequest) request;
String url = r.getRequestURI().substring(r.getContextPath().length());
//Remove extensions if present any
int index = url.indexOf('.');
if(index > 0)
url = url.substring(0,url.indexOf('.'));
/*Matching string this should be replaced by url pattern matching of spring.*/
if (methodsToCheck.containsKey(url)){
Method method = methodsToCheck.get(url);
Map<String, String[]> requestParameterMap = r.getParameterMap();
Map<String,Boolean> isParamPresent = new HashMap<String,Boolean>();
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
if (!requestParam.name().isEmpty())
isParamPresent.put(requestParam.name(), false);
else
isParamPresent.put(requestParam.value(), false);
}
}
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
String name = null;
if (!requestParam.name().isEmpty())
name=requestParam.name();
else
name=requestParam.value();
if (requestParameterMap.containsKey(name)){
isParamPresent.put(name, true);
}
}
}
for (Map.Entry<String, Boolean> entry : isParamPresent.entrySet()){
if (!entry.getValue()){
System.out.println(entry.getKey() + " is either missing or mis-spelled");
}
}
}
chain.doFilter(request, response);
}
}
configured as follows:
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
Declare following custom annotation.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PrintParamMismatch {
}
Define following bean which would populate on startup
#Bean(name="printMismatchMethods")
#Autowired
public HashMap<String,Method> printParamMismatchMethods(BeanFactory beanFactory){
HashMap<String,Method> methods = new HashMap<String,Method>();
Map<String, RequestMappingHandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
(ListableBeanFactory)beanFactory,
RequestMappingHandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
ArrayList<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(handlerMappings);
RequestMappingHandlerMapping mappings = matchingBeans.get("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mappings.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethod : handlerMethods.entrySet()){
RequestMappingInfo info = handlerMethod.getKey();
HandlerMethod hMethod = handlerMethod.getValue();
Method method = hMethod.getMethod();
if (method.getAnnotation(PrintParamMismatch.class) != null){
String path = info.getPatternsCondition().toString();
path = path.substring(1,path.length());
path = path.substring(0,path.length()-1);
methods.put(path, method);
}
}
}
return methods;
}
This, I think is generic enough to show debug information for now. However we need to store and match Patterns instead of url string.
So I used divide and rule and solved my issue. I commented top half of the parameters and ran, to check if my controller gets called. It did. then I added 1/4th, then 1/8th and found that I missed a parameter.
That along with #ArsianAnjum's answer is good for debugging. #Aji's answer is the long term solution. I should be using that.
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
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;
}
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;
}
I am setting up a simple RESTful controller for a Todo resource with an XML representation. It all works great - until I try to redirect. For example, when I POST a new Todo and attempt to redirect to its new URL (for example /todos/5, I get the following error:
Error 500 Unable to locate object to be marshalled in model: {}
I do know the POST worked because I can manually go to the new URL (/todos/5) and see the newly created resource. Its only when trying to redirect that I get the failure. I know in my example I could just return the newly created Todo object, but I have other cases where a redirect makes sense. The error looks like a marshaling problem, but like I said, it only rears itself when I add redirects to my RESTful methods, and does not occur if manually hitting the URL I am redirecting to.
A snippet of the code:
#Controller
#RequestMapping("/todos")
public class TodoController {
#RequestMapping(value="/{id}", method=GET)
public Todo getTodo(#PathVariable long id) {
return todoRepository.findById(id);
}
#RequestMapping(method=POST)
public String newTodo(#RequestBody Todo todo) {
todoRepository.save(todo); // generates and sets the ID on the todo object
return "redirect:/todos/" + todo.getId();
}
... more methods ...
public void setTodoRepository(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
private TodoRepository todoRepository;
}
Can you spot what I am missing? I am suspecting it may have something to do with returning a redirect string - perhaps instead of it triggering a redirect it is actually being passed to the XML marshaling view used by my view resolver (not shown - but typical of all the online examples), and JAXB (the configured OXM tool) doesn't know what to do with it. Just a guess...
Thanks in advance.
This happend because redirect: prefix is handled by InternalResourceViewResolver (actually, by UrlBasedViewResolver). So, if you don't have InternalResourceViewResolver or your request doesn't get into it during view resolution process, redirect is not handled.
To solve it, you can either return a RedirectView from your controller method, or add a custom view resolver for handling redirects:
public class RedirectViewResolver implements ViewResolver, Ordered {
private int order = Integer.MIN_VALUE;
public View resolveViewName(String viewName, Locale arg1) throws Exception {
if (viewName.startsWith(UrlBasedViewResolver.REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(UrlBasedViewResolver.REDIRECT_URL_PREFIX.length());
return new RedirectView(redirectUrl, true);
}
return null;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}