I created a message handler which will log the request and the response. ideally I want to
public class LoggingMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LogRequest(request);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
LogResponse(response);
return response;
});
}
private void LogRequest(HttpRequestMessage request)
{
var writer = request.GetConfiguration().Services.GetTraceWriter();
var content = request.Content;
(content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
writer.Trace(request, "request", System.Web.Http.Tracing.TraceLevel.Info, t =>
{
t.Message = x.Result;
});
});
}
private void LogResponse(HttpResponseMessage response)
{
var request = response.RequestMessage;
var writer = request.GetConfiguration().Services.GetTraceWriter();
var content = response.Content;
(content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
writer.Trace(request, "response", System.Web.Http.Tracing.TraceLevel.Info, t =>
{
t.Status = response.StatusCode;
t.Message = x.Result;
});
});
}
}
and here is my client code.
public ActionResult Index()
{
var profile = Client.GetAsync("Vendor").Result.EnsureSuccessStatusCode().Content.ReadAsAsync<VendorProfileModel>().Result;
return View(profile);
}
Logging appears to be working. However, when this handler is registered my client code returns an empty object. If I remove this handler the model is successfully read from the response and displayed on screen.
Is there a way to read the content and display the results on the client?
after a few more days for digging around on the net I finally found the root problem and a solution. First the problem:
everything in webapi is async
my action uses Controller.User which in turn is calling Thread.CurrentPrinciple
I am using ITraceWriter as my logging abstraction
apparently there is a bug in the ITraceWriter mechanicism where the current profile is not propagated across threads. therefore, i loose the principle when i get to my controller action. therefore, my query returns an empty result, rather than a fully populated result.
solution: don't use ITraceWriter to log messages. It would have been nice to use the built in mechanics, but that doesn't work. here is the link to the same issue which provides more detail/context.
https://aspnetwebstack.codeplex.com/workitem/237
Related
I have a web api to consume the data coming from android mobile. This web api will consume the multi part file from along with the form data the web api request. I followed this article to archive.
[CustAuthAsync]
public async Task<HttpResponseMessage> SaveEHSInspectionData()
{
try
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(root);
//do stuff
var res = await Request.Content.ReadAsMultipartAsync(provider);
// DO SOME STUFF
}
catch (Exception exp)
{
}
return Request.CreateResponse(HttpStatusCode.OK, result);
}
I wanted to do the custom access validation for this web api, so implemented a filter to validate the request.
I have the filter like below
public class CustAuthAsyncAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
InternalOnExecutingAsync(actionContext);
}
}
The internal method like this
protected void InternalOnExecutingAsync(HttpActionContext actionContext)
{
var authValue = actionContext.Request.Headers;
if (authValue.Contains("CustomAccessToken"))
{
string token = authValue.GetValues("CustomAccessToken").First();
var result = // doing some decription
if (result != null)
{
bool validationResult = // validation with database
if (!validationResult)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{ ReasonPhrase = "Invalid token" };
}
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{ ReasonPhrase = "Invalid token" };
}
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{ ReasonPhrase = "Unauthorized Request" };
}
These implementations are working fine in API Client Tools (Example: Postman) if the validation passes, allows the request to the method.
Postman Response screen shot
This is not working in mobile app, Saying the response message as Unauthorized Access. and not allowing the request to the method even the custom access validations are passed.
FYI : This method is working fine in mobile without filter
Help me to get this works in mobile app also.
Thanks in advance.
Your using the wrong type of filter to manage access. You should use an authorization filter. Besides you can't have an async method to authorize. You have to make the calling client wait for clearance. This may cause the side effects you're experiencing.
I'm not sure this has any to do with fact that it's a mobile application, however the authorization phase ir prior to the processing of the request. Verify that your are not using any other form of authorization in your project.
You should implement an authorization filter by inheriting AuthorizeAttribute and overriding IsAuthorized(HttpActionContext actionContext) method:
public class CustAuthAsync : AuthorizeAttribute
{
public CustAuthAsync()
{
///Some initialization if required. Otherwise, not necessary to declare the constructor..
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var authValue = actionContext.Request.Headers;
if (authValue.Contains("CustomAccessToken"))
{
string token = authValue.GetValues("CustomAccessToken").First();
var result = // doing some decription
if (result != null)
{
return //database validation
}
else
{
return false;
//No need to create special unauthorized response. You should not hint the reason at this point. You can do this in the HandleUnauthorizedRequest method.
}
}
else
{
return false;//No need to create special unauthorized response.
}
}
}
You can use this attribute to decorate your controllers. You can even pass parameter in the constructor for more granular control on access management, like a required role to access de controller.
The database works. It does actually insert the new record, but when I use CreatedAtRoute(), I always get a 500 back from the client. Why?
My controller's Get:
[Route("api/[controller]")]
public class IngredientController : Controller
{
private SimpleCookbookDbContext db { get; set; }
public IngredientController(SimpleCookbookDbContext context)
{
db = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Ingredient>> Get()
{
return await db.Ingredients.ToListAsync();
}
// GET api/values/5
[HttpGet("{id}", Name = "GetIngredient")]
public async Task<Ingredient> Get(int id)
{
return await db.Ingredients.SingleOrDefaultAsync(i => i.Id == id);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Ingredient ingredient)
{
try
{
var res = await IM.CreateAsync(ingredient);
if (!res.Success)
{
return HttpBadRequest(res.Errors);
}
}
catch(Exception)
{
return new HttpStatusCodeResult((int)HttpStatusCode.InternalServerError);
}
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id });
}
}
I tried debugging this. Yes, it would return the HttpBadRequest if the ingredient I'm trying to insert already exists.
I tried putting a breakpoint inside the catch block, and I'm not getting there, so I assume there was no error from the database.
The record does get inserted to the database. I do get to the line return CreatedAtRoute(...); but I get a 500 back. (I set a breakpoint there, too).
Now, I'm using fiddler. My request is this:
POST /api/ingredient HTTP/1.1
Host: localhost:55303
Content-Type: application/json;charset=utf-8
{"id":0, "name": "rosemary", "description": "rosemary"}
I also removed the double quotes on the property names, and I still get the same 500.
I do have camel-casing resolved at the Startup:
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
I think I showed all relevant code. If you need more, please let me know.
UPDATE
CreatedAtRoute has an overload taking in three parameters:
return CreatedAtRoute("GetIngredient", new { controller="Ingredient", id = ingredient.Id }, ingredient);
The last parameter is an object, which you could create dynamically or pass back your entire entity, depending on what you want to expose back.
It's strange how there's a 2-parameter variant that would result in a strange 500 response.
I have a service layer called GatewayService which calls another WebApi to get the product information and manipulate the Inventory in the response and return the same response to the caller.
Here is the code I have. The problem that I have is, I can't use Request.CreateResponse(...) which will give me a compilation error because GatewayService does not inherit ApiController.
Is there a way to update the response and return as HttpResponseMessage?
public class GatewayService
{
// Code Removed for bravity
public HttpResponseMessage Get(SingleProductSearcRequest request)
{
var response = productServiceWebApi.Get(request); // Returns HttpResponseMessage
var p = response.Content.ReadAsAsync<JObject>().Result;
p["Inventory"] = "Not Available";
return Request.CreateResponse(p); // COMPILER ERROR!!!
}
}
Request.CreateResponse() is just an extension method for HttpRequest. You can manually construct an HttpResponseMessage as well as give it content by doing something like:
var p = response.Content.ReadAsAsync<JObject>().Result;
HttpResponseMessage message = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
message.Content = new ObjectContent(p);
You can transfer headers and other information over as well, if necessary. Depending on the need, there's also StringContent, etc.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
CORS with WebAPI for XmlHttpRequest
I'm trying to implement cross-domain ajax post to my webApi project. I had few troubles with that:
1. I always was getting 204 error until changed my webapi action from
public void submit(Submission model)
to
public bool submit(Submission model)
don't know why, but now I'm getting 200 OK status
2. Still my ajax firing error callback.
3. Long time ago I solved this kind of error of cross-domain posting by adding
HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
to my controller. But now in webApi i'm inherent from : ApiController and this trick doesn't work. Shows me compiler Error an object reference is required for the non-static field, method, or property"System.Web.HttpContext.Response.get"
I have tryed to post via dataType: 'JSONP' but I get null model.
Here goes Javascript request:
var model = {
"type": $("#model-type").val(),
"subject": $("#subject-text").val(),
"body": $("#body-text").val()
};
$.ajax({
type: "POST",
dataType: 'JSONP',
url: $("#submit-url").val(),
data: model,
success: function () {
alert("Succesfully submitted");
},
error: function () {
alert("Error...");
}
});
What I'm doing wrong?
SOLVED
Thanks to everybody for helping me out. I found solution in one of the comment links. I used following approach, which I find pretty simple.
Source:
Implementing CORS support in ASP.NET Web APIs
What I made:
1. Created new Class in my project: CorsHandler.cs and just copy-pasted following code:
public class CorsHandler : DelegatingHandler
{
const string Origin = "Origin";
const string AccessControlRequestMethod = "Access-Control-Request-Method";
const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
bool isCorsRequest = request.Headers.Contains(Origin);
bool isPreflightRequest = request.Method == HttpMethod.Options;
if (isCorsRequest)
{
if (isPreflightRequest)
{
return Task.Factory.StartNew(() =>
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
string accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault();
if (accessControlRequestMethod != null)
{
response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod);
}
string requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders));
if (!string.IsNullOrEmpty(requestedHeaders))
{
response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
}
return response;
}, cancellationToken);
}
else
{
return base.SendAsync(request, cancellationToken).ContinueWith(t =>
{
HttpResponseMessage resp = t.Result;
resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
return resp;
});
}
}
else
{
return base.SendAsync(request, cancellationToken);
}
}
}
Opened my Global.asax and modifyed Application_Start :
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsHandler());
}
Pay attention to the last line in action.
This approach is compatible with MVC3 and .NET 4.0. Works great, now I can handle "success" and "error" callbacks in ajax.
Answers to your questions respectively:
Status 204 is not an error, which means no content to return but everything's good. Here's the definition of 204 in RFC2616
10.2.5 204 No Content
The server has fulfilled the request but does not need to return an
entity-body, and might want to return updated metainformation. The
response MAY include new or updated metainformation in the form of
entity-headers, which if present SHOULD be associated with the
requested variant.
If the client is a user agent, it SHOULD NOT change its document view
from that which caused the request to be sent. This response is
primarily intended to allow input for actions to take place without
causing a change to the user agent's active document view, although
any new or updated metainformation SHOULD be applied to the document
currently in the user agent's active view.
The 204 response MUST NOT include a message-body, and thus is always
terminated by the first empty line after the header fields.
Could you articulate what's the error you met? The ASP.NET Web API currently doesn't have a JSONP formatter out of box. Here's some 3rd part implementation:
http://www.west-wind.com/weblog/posts/2012/Apr/02/Creating-a-JSONP-Formatter-for-ASPNET-Web-API
http://www.nuget.org/packages/WebApi.JsonP
I hope they're helpful.
In Web API the way in which you refers to an Response is not through HttpContext. There are multiple ways to access.
The first option is to define action return HttpResponse directly.
public HttpResponseMessage Get(int id)
{
var response = this.Request.CreateResponse();
response.StatusCode = HttpStatusCode.OK;
response.Headers.Add("Access-Control-Allow-Origin", "*");
return response;
}
The second option is to use ActionFilter:
// define action filter for cross domain
public class CrossDomainActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
bool needCrossDomain = true;
if (needCrossDomain)
{
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
}
base.OnActionExecuted(actionExecutedContext);
}
}
// At Controller
// GET api/values/5
[CrossDomainActionFilter]
public string Get(int id)
{
return "value";
}
The last option is to use MessageHandler:
public class CrossDomainMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
response.Headers.Add("Access-Control-Allow-Origin", "*");
return response;
}
}
If you want to send information to another domain from ajax then you need to use jsonp (note this only works with get requests not post requests). Another alternative (if you are in control of both domains) is to use ARR (application request routing) to trick the browser into thinking the request is local then using ARR to rewrite the request to another domain. Using this technique you can use simple ajax gets and posts like normal.
In my MVC website, I am creating a small forum. For a single post I am rendering my "Single(Post post)" action in my "PostController" like below
<% Html.RenderAction<PostController>(p => p.Single(comment)); %>
Also When a user reply a post I am sending reply as an ajax request to my "CreatePost" action then return "Single" view as result of this action like below
public ActionResult CreatePostForForum(Post post)
{
//Saving post to DB
return View("Single", postViewData);
}
When I do like that only the view is being rendered, Codes in "Single" Actions body isn't beig executed.
What is the best way to do this?
Also I want to return "Single" action result as string in my JsonObject like below
return Json(new{IsSuccess = true; Content= /*HERE I NEED Single actions result*/});
You can use something like this, but be very careful with this. It can actually cause badly traceable errors (for example when you forget to explicitly set view name in Single method).
public ActionResult Single(PostModel model) {
// it is important to explicitly define which view we should use
return View("Single", model);
}
public ActionResult Create(PostModel model) {
// .. save to database ..
return Single(model);
}
Cleaner solution would be to do the same as if it was post from standard form - redirect (XMLHttpRequest will follow it)
For returning ajax views wrapped in json I use following class
public class AjaxViewResult : ViewResult
{
public AjaxViewResult()
{
}
public override void ExecuteResult(ControllerContext context)
{
if (!context.HttpContext.Request.IsAjaxRequest())
{
base.ExecuteResult(context);
return;
}
var response = context.HttpContext.Response;
response.ContentType = "application/json";
using (var writer = new StringWriter())
{
var oldWriter = response.Output;
response.Output = writer;
try
{
base.ExecuteResult(context);
}
finally
{
response.Output = oldWriter;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(serializer.Serialize(new
{
action = "replace",
html = writer.ToString()
}));
}
}
}
It is probably not the best solution, but it works quite well. Note that you will need to manually set View, ViewData.Model, ViewData, MasterName and TempData properties.
My recommendation:
Post your forum reply (and whatever options) via Ajax.
Return your JSONResult, using this method: ASP MVC View Content as JSON to render your content.
In the OnSuccess handler of your ajax call, check if IsSuccess is true. If successful, append the content to the appropriate container using JQuery