Custom error handling in webmethod returning XmlDocument - asp.net

I'm working with a client who has a web method like this:
[WebMethod]
public XmlDocument Send(string stuff)
{
// ...
}
At present, there's a class of exceptions which occur which the code is re-throwing, triggering ASP.Net's standard handling of exceptions.
We'd like to change it so that the webmethod still returns status code 500, but with some text/plain diagnostic information we provide rather than the default ASP.Net stuff.
What's the appropriate way to do that?
I've made it work, using Context.Response.End like this:
[WebMethod]
public XmlDocument Send(string stuff)
{
try
{
// ...normal processing...
return xmlDocument;
}
catch (RelevantException)
{
// ...irrelevant cleanup...
// Send error
Context.Response.StatusCode = 500;
Context.Response.Headers.Add("Content-Type", "text/plain");
Context.Response.Write("...diagnostic information here...");
Context.Response.End();
return null;
}
}
But that feels hacky, so I'm hoping there's a better answer.

But that feels hacky, so I'm hoping there's a better answer.
It feels hacky because it is hacky.
The better answer is: Return XML, like you said you would, with whatever information it is you want to include. The service returns XML, it was intended for code, not people, to consume.
[WebMethod]
public XmlDocument Send(string stuff)
{
try
{
// ...normal processing, creates xmlDocument...
return xmlDocument;
}
catch (RelevantException)
{
// ...irrelevant cleanup...
// ...error processing, creates xmlDocument...
Context.Response.StatusCode = 500;
return xmlDocument;
}
}

Related

Get request body in web api IExceptionHandler

I'm trying to write a global error handler for ASP.NET web api that is able to log the request details of requests that cause unhandled exception in my api. I've registered the below GlobalExceptionHandler class in my OWIN startup class, but I'm unable to retrieve the content of any data posted in the body of requests.
public class GlobalExecptionHander : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var body = context.Request.Content.ReadAsStringAsync().Result;
//body here is an empty string
context.Result = new UnhandledErrorActionResult
{
Request = context.Request,
};
}
}
In my startup class
config.Services.Replace(typeof(IExceptionHandler), new GlobalExecptionHander());
Since I just came across this exact problem, I was amazed to find this question without an answer! I hope you've managed to solve the problem after all this time. I'd still like to answer this question regardless.
The thing is that by the time your GlobalExceptionHandler handles the exception, something (like Newtonsoft Json or any other request handler) has already read the contentstream of the HTTP request. When the stream is read, you cannot read it again, unless there was some way to reset that stream to its initial position...
public override void Handle(ExceptionHandlerContext context)
{
string requestContent = "";
using(System.IO.Stream stream = context.Request.Content.ReadAsStreamAsync().Result)
{
// Set the stream back to position 0...
if (stream.CanSeek)
{
stream.Position = 0;
}
// ... and read the content once again!
requestContent = context.Request.Content.ReadAsStringAsync().Result;
}
/* ..Rest of code handling the Exception.. */
}
The reason requestContent is outside that using block, is because the stream gets disposed after the block closes. You could also get rid of using and call stream.Dispose() after you've done reading the content.

How can I use the AsyncCTP with an TFS APM Method (Query.Begin/EndQuery)?

Would like to try using AsyncCTP with TFS. Currently have a long running method that calls RunQuery on a TFS Query instance.
Query exposes the APM methods BeginQuery() and EndQuery(). As I understand it, the recommended approach to wrap these using AsyncCTP is something like: (example from docs)
Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null);
Further, have wrapped it in an extension method as in the docs so my actual method looks like:
public static Task<WorkItemCollection> RunQueryAsync(this Query query)
{
if (query== null)
throw new ArgumentNullException("Query");
return Task<WorkItemCollection>.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
}
...but this fails to compile. Getting an "invalid argument" intellisense error that, frankly, I can't really understand because the types and format look correct. One possible issue might be that the Query APM methods expect an ICanceleableAsyncResult whereas the Task factory is expecting an IAsyncResult -- but looking at the TFS API, ICanceleableAsyncResult is a specialization of IAsyncResult.
Not sure whether i'm doing it wrong or its just not possible. Would love to be able to do it the AsyncCTP way but may have to go back to the APM pattern -- ugh!
Update: My Nito.AsyncEx library now includes a TeamFoundationClientAsyncFactory type, which can be used instead of rolling your own implementation below.
The TFS API is not strictly following the APM pattern because it does not take a state parameter, and this is preventing the built-in TaskFactory.FromAsync from working.
You'll have to write your own FromAsync equivalent, which can be done using TaskCompletionSource:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Client;
public static class TfsUtils<TResult>
{
public static Task<TResult> FromTfsApm(Func<AsyncCallback, ICancelableAsyncResult> beginMethod, Func<ICancelableAsyncResult, TResult> endMethod, CancellationToken token)
{
// Represent the asynchronous operation by a manually-controlled task.
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
try
{
// Begin the TFS asynchronous operation.
var asyncResult = beginMethod(Callback(endMethod, tcs));
// If our CancellationToken is signalled, cancel the TFS operation.
token.Register(asyncResult.Cancel, false);
}
catch (Exception ex)
{
// If there is any error starting the TFS operation, pass it to the task.
tcs.TrySetException(ex);
}
// Return the manually-controlled task.
return tcs.Task;
}
private static AsyncCallback Callback(Func<ICancelableAsyncResult, TResult> endMethod, TaskCompletionSource<TResult> tcs)
{
// This delegate will be invoked when the TFS operation completes.
return asyncResult =>
{
var cancelableAsyncResult = (ICancelableAsyncResult)asyncResult;
// First check if we were canceled, and cancel our task if we were.
if (cancelableAsyncResult.IsCanceled)
tcs.TrySetCanceled();
else
{
try
{
// Call the TFS End* method to get the result, and place it in the task.
tcs.TrySetResult(endMethod(cancelableAsyncResult));
}
catch (Exception ex)
{
// Place the TFS operation error in the task.
tcs.TrySetException(ex);
}
}
};
}
}
You can then use it in extension methods as such:
using System.Threading;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
public static class TfsExtensions
{
public static Task<WorkItemCollection> QueryAsync(this Query query, CancellationToken token = new CancellationToken())
{
return TfsUtils<WorkItemCollection>.FromTfsApm(query.BeginQuery, query.EndQuery, token);
}
}

Spring MVC Validation - Avoiding POST-back

I'd like to validate a Spring 3 MVC form. When an element is invalid, I want to re-display the form with a validation message. This is pretty simple so far. The rub is, when the user hits refresh after an invalid submission, I don't want them to POST, I want them to GET. This means I need to do a redirect from the form POST (submission) to re-display the form with validation messages (the form is submitted via a POST).
I'm thinking the best way to do this is to use SessionAttributeStore.retrieveAttribute to test if the form is already in the user's session. If it is, use the store form, otherwise create a new form.
Does this sound right? Is there a better way to do this?
To solve this problem, I store the Errors object in the session after a redirect on a POST. Then, on a GET, I put it back in the model. There are some holes here, but it should work 99.999% of the time.
public class ErrorsRedirectInterceptor extends HandlerInterceptorAdapter {
private final static Logger log = Logger.getLogger(ErrorsRedirectInterceptor.class);
private final static String ERRORS_MAP_KEY = ErrorsRedirectInterceptor.class.getName()
+ "-errorsMapKey";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView mav)
throws Exception
{
if (mav == null) { return; }
if (request.getMethod().equalsIgnoreCase(HttpMethod.POST.toString())) {
// POST
if (log.isDebugEnabled()) { log.debug("Processing POST request"); }
if (SpringUtils.isRedirect(mav)) {
Map<String, Errors> sessionErrorsMap = new HashMap<String, Errors>();
// If there are any Errors in the model, store them in the session
for (Map.Entry<String, Object> entry : mav.getModel().entrySet()) {
Object obj = entry.getValue();
if (obj instanceof Errors) {
if (log.isDebugEnabled()) { log.debug("Adding errors to session errors map"); }
Errors errors = (Errors) obj;
sessionErrorsMap.put(entry.getKey(), errors);
}
}
if (!sessionErrorsMap.isEmpty()) {
request.getSession().setAttribute(ERRORS_MAP_KEY, sessionErrorsMap);
}
}
} else if (request.getMethod().equalsIgnoreCase(HttpMethod.GET.toString())) {
// GET
if (log.isDebugEnabled()) { log.debug("Processing GET request"); }
Map<String, Errors> sessionErrorsMap =
(Map<String, Errors>) request.getSession().getAttribute(ERRORS_MAP_KEY);
if (sessionErrorsMap != null) {
if (log.isDebugEnabled()) { log.debug("Adding all session errors to model"); }
mav.addAllObjects(sessionErrorsMap);
request.getSession().removeAttribute(ERRORS_MAP_KEY);
}
}
}
}
It's not clear from your question but it sounds like your GET and POST actions are mapped to the same handler. In that case you can do something like:
if ("POST".equalsIgnoreCase(request.getMethod())) {
// validate form
model.addAttribute(form);
return "redirect:/me.html";
}
model.addAttribute(new MyForm());
return "/me.html";
In the JSP check if there are any error on the form and display as needed.
Such approach is called PRG (POST/REdirect/GET) design pattern I explained it few days ago as one of the answers:
Spring MVC Simple Redirect Controller Example
Hope it helps :)

How do i improve the legacy code implementation, Response.Redirect?

Some scenarios to ponder. There is a legacy code which has following implementation Example1 and Example2. If we try to implement MSDN recommendation then the legacy code fails.
Here is a Legacy code example:
Example 1:
void Page_Load() {
.... some code
if(condition) {
/// some condition
} else {
RedirectPage(url);
}
// another code block
// some other conditions.
}
Example 2:
a. File1.ascx
void Page_Load() {
try {
.. some code
base.CheckPreference();
RedirectPage(defaultPage);
}
catch(Exception ex) {
ExceptionHandling.GetErrorMessage(ex);
}
}
b. BaseClass.cs // this is the base class
void CheckPreference() {
try {
if(condition) {
RedirectPage(url1);
} else if(condition2) {
RedirectPage(url2);
} else {
// update session
}
}
catch(Exception ex) {
ExceptionHandling.GetErrorMessage(ex);
throw;
}
}
void RedirectPage(string url) {
Response.Redirect(url);
}
One possible way is to add a boolean field in the class e.g endExecution, set the field to true whenever RedirectPage is called.
We have to update RedirectPage code see code snippet below:
// Updated code - MSDN recommendation.
void RedirectPage(url) {
Response.Redirect(url, false);
this.Context.ApplicationInstance.CompleteRequest();
endExecution = true;
}
Please suggest some other better ways to improve the legacy code implementation.
Probably the most unintuitive thing for folks issuing a redirect is that in our minds we've already returned from the method what we call Respond.Redirect (or whatever the equivilent is in your language/platform of the day. All we've done is call a method.
Bottom line is that you have to stop processing the request to avoid trying to commit to responses for the same request. That would throw an exception on just about any platform I've worked with.
ASP.NET MVC improved this with the ActionResponse so that you are returning from the method (and terminating the remainder of request processing) with code that looks like this:
return Redirect(url);
Bottom line is that you need to get in the habit of returning from your event right after you perform your redirect. Any deviation from that habit needs to be documented in the code why. This will help make the application perform the way you expect.
The approach that you've taken is perfectly reasonable.

Is it possible to style the Windows Identity Foundation postback page?

Is it possible to style the Windows Identity Foundation postback page?
This is the page that comes across as blank after you successfully login and the url is similar to https://sts.address.com/?wa=wsignin1.0&wtrealm=https....
I read the following article, http://www.paraesthesia.com/archive/2011/01/31.aspx, which led me to using dotPeek on the Microsoft.IdentityModel assembly. Which shows me that all the ProcessSignInResponse message does, is the following:
public static void ProcessSignInResponse(SignInResponseMessage signInResponseMessage, HttpResponse httpResponse)
{
if (signInResponseMessage == null)
throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("signInResponseMessage");
else if (httpResponse == null)
{
throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("httpResponse");
}
else
{
signInResponseMessage.Write(httpResponse.Output);
httpResponse.Flush();
httpResponse.End();
}
}
The signInResponseMessage.Write method does the following:
public override void Write(TextWriter writer)
{
if (writer == null)
{
throw DiagnosticUtil.ExceptionUtil.ThrowHelperArgumentNull("writer");
}
else
{
this.Validate();
writer.Write(this.WriteFormPost());
}
}
As you can see, in essence, all that is performed, is to write the content of WriteFormPost to the response stream.
So I am, as we speak, changing my "ProcessSignIn" method to return the HTML to be output, instead of calling FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse.
So, I have changed my method essentially from this:
public static void ProcessSignIn(SignInRequestMessage signInRequest, HttpResponse httpResponse)
{
FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(signInResponseMessage, httpResponse);
}
To:
public static string ProcessSignIn(SignInRequestMessage signInRequest)
{
return signInResponseMessage.WriteFormPost();
}
Of course, the SignInResponseMessage should have provided a cleaner method of returning just the "main" content of what you want to write to your form post, but getting the
HTML form as a string at least makes it easier to modify the result before returning it to the client with Response.Write(result).
I don't know if this is a documented feature, but I will suggest the following as a jumping-off point:
If your code looks anything at all like mine, you have a line of code that looks like:
FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(responseMessage, HttpContext.Current.Response)
The second parameter to ProcesssignInResponse is an HttpResponse object. I tried, unsuccessfully, to find an answer to your question, by trying to pass in a custom HttpResponse message in order to capture the output so we can manipulate it however you like:
Dim myStringbuilder As New StringBuilder
Dim myStringWriter As New IO.StringWriter(myStringbuilder)
Dim myResponse As New Web.HttpResponse(myStringWriter)
If you pass in myResponse to ProcessSignInResponse, the following exception is thrown:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.HttpResponse.End()
at Microsoft.IdentityModel.Web.FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse(SignInResponseMessage signInResponseMessage, HttpResponse httpResponse)
at Logon_App.LCLoginBase.issueTokenAndRedirect(logonParamsStruct& logonParams) in C:\Logon App\Logon App\Code\LCLogin\LCLoginBase.vb:line xxx

Resources