Handling concurrency exceptions with external API calls - asp.net-mvc

I have the following POST edit action method, which mainly perform two Update actions:-
Edit the object on the external system suing API calls.
Edit the object on our system database.
[HttpPost]
public ActionResult Create(RackJoin rj, FormCollection formValues)
{string controllername = RouteData.Values["controller"].ToString();
if (ModelState.IsValid)
{ var message = "";
var status = "";
long assetid = new long();
XmlDocument doc = new XmlDocument();
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiUserName"];
query["password"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiPassword"];
string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
var url = new UriBuilder(apiurl);
url.Query = query.ToString();
try
{
string xml = client.DownloadString(url.ToString());
doc.LoadXml(xml);
status = doc.SelectSingleNode("/operation/operationstatus").InnerText;
message = doc.SelectSingleNode("/operation/message").InnerText;
}
catch (WebException ex)
{
ModelState.AddModelError(string.Empty, "Error occurred:" + ex.InnerException);
}
}
if (status.ToUpper() == "SUCCESS")
{
repository.InsertOrUpdateRack(rj.Rack, User.Identity.Name, rj.Resource.RESOURCEID);
repository.Save();
return RedirectToAction("Index");
}
else
{
ModelState.AddModelError(string.Empty, message.ToString());
}
}
}
catch (DbUpdateConcurrencyException ex)
{
As shown in the above code I will not do a repository.save() to update the object on our system, unless the API return a “success”.
But currently I am facing the following problem:-
If the API return a “success” but a concurrency exception occurred, then the API will update the object on the external system, but the object will not be updated on our system?
So is there a way to handle this situation?

There's no easy way to solve this situation. One way to handle it would be to ask the designers of the external API expose a method allowing to commit the transaction done in a previous call. Basically your first call will make modifications to the external system but with some boolean flag indicating that those changes are still pending. Then you update your system and in case of success you would call the external API to flag the data from pending to valid.
If you have no control over the external API and it makes the changes to the data from the first call irreversible, then I am afraid that you do not have much choices left. You might remember the state of the object you are modifying on the external system before calling the API and in case of an exception on your system, revert back to the previous state by calling the API with the previous values.

Related

Is there an edge case for EF6 to not persist to db, but no exceptions thrown

I have a weird edge-case that happens probably less than 1% which I can't wrap my head around a possible cause or replicate.
A Xamarin forms mobile app calls an endpoint to insert data into the db, if successful everyone is happy, if not, retry later - the tech stack of the API is a bit old, MVC 5 and entity framework 6, but shouldn't be a problem.
The endpoint has some fundamental fixes needed, but showing for completeness:
public async Task<HttpResponseMessage> Post(SubmitDataDTO submitData)
{
db = new DBContext();
try
{
var parentId = Guid.NewGuid();
foreach (var childItem in submitData.Children)
{
var child = new Children()
{
Id = Guid.NewGuid(),
ParentId = parentId,
//properties..
};
db.Children.Add(child);
}
var parent = new Parent()
{
Id = incomingId,
//properties..
};
db.Parent.Add(parent);
await db.SaveChangesAsync();
}
catch (System.Data.Entity.Validation.DbEntityValidationException ex)
{
//LOG VALIDATION ERROR
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
catch (Exception exc)
{
//LOG ERROR
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
The mobile app side is also pretty straight forward, if response.IsSuccessStatusCode is true, mark the submission as successfully processed. What we are seeing is that the mobile app is showing it was successfully submitted, but the data isn't available on the server.
Only explanation I can see, there is either an edge case where the SaveChanges doesn't persist with no exception and the OK is sent, or there is some edge case where IIS/endpoint sends an success, but the endpoint was never processed. I'm really stumped.

MVC Post Request how to get Request Content if it's different to that expected by the Default ModelBinder

I have this MVC WebApi action:
PostTrips(List<Trip> trips)
When a list of trips is sent through everything works fine. If, however, someone is trying to post incorrect data, e.g just an object {} then trips is null - this is fine, but I would like to log the data that the user tried to push.
I tried to get it using string requestData = Request.Content.ReadAsStringAsync().Result; but it can only be called once, and I guess the default model binder is calling it to try an map it to my List<Trip>, as when I call it, the result is always null, even though I know I'm passing something in.
Does anyone know of another way to get the posted data again?
I got around this my removing the parameter List<Trip> trips from the action so I had:
public async Task<HttpResponseMessage> PostTrips()
{
}
This bypasses the default model binder and allows you to get the unmodified request content using:
string requestContent = await Request.Content.ReadAsStringAsync();
You can then do what ever you need with this - I wanted to log the data for error tracking.
To create the actual List<Trip> trips I then used Newtonsoft.Json to deserialise the string into a list:
List<TravelTrackerTrip> appTrips = JsonConvert.DeserializeObject<List<TravelTrackerTrip>>(requestContent);
Full example:
public async Task<HttpResponseMessage> PostTrips()
{
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
List<Trip> appTrips = null;
string requestContent = await Request.Content.ReadAsStringAsync();
try
{
appTrips = JsonConvert.DeserializeObject<List<Trip>>(requestContent);
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
try
{
//Success - do whatever we need
}
catch(Exception ex)
{
//ERROR LOGGING HERE...
//QUIT - Return failure response
}
//Return success response
}

Is it a bad practice to have separate try/catch blocks inside the same Action method

I have the following Delete Action method, which mainly perform two separate tasks:-
Delete a record from a Third party application using API call.
Delete a record from the database on our own system using entity framework.
My action method looks as follow:-
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
var message = "";
var status = "";
var tag = "";
Resource resource = new Resource();
try
{
Rack rack = repository.FindRack(id);
tag = rack.Technology.Tag;
resource = repository.GetResource(rack.Technology.IT360ID.Value);
}
catch (NullReferenceException)
{
return Json(new
{
IsSuccess = "False"
}, JsonRequestBehavior.AllowGet);
}
catch (DbUpdateException)
{
return Json(new
{
IsSuccess = "AlreadyUsed"
}, JsonRequestBehavior.AllowGet);
}
using(var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
query["username"] = "testuser";
query["assetType"] = resource.ComponentDefinition.ComponentType.COMPONENTTYPENAME;
query["operation"] = "DeleteAsset";
query["assetName"] = resource.RESOURCENAME;
var url = new UriBuilder("http://win-spdev:8400/servlets/AssetServlet");
url.Query = query.ToString();
try
{
string xml = client.DownloadString(url.ToString());
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
status = doc.SelectSingleNode("/operation/operationstatus").InnerText;
message = doc.SelectSingleNode("/operation/message").InnerText;
}
catch (WebException ex)
{}
}
if (status.ToUpper() == "SUCCESS")
{
try
{
repository.DeleteRack(id, User.Identity.Name);
repository.Save();
return Json(new
{
IsSuccess = "True", id = id, description = tag
}, JsonRequestBehavior.AllowGet);
}
catch (NullReferenceException)
{
return Json(new
{
IsSuccess = "False"
}, JsonRequestBehavior.AllowGet);
}
catch (DbUpdateException)
{
return Json(new
{
IsSuccess = "AlreadyUsed"
}, JsonRequestBehavior.AllowGet);
}
}
return RedirectToAction("Delete", new
{
id = id
});
}
As since I am using the entity framework to perform the deletion and also a API call, so I ended up with separate try/catch blocks . so does my action method logic consider a poor design since I am having multiple try/catch blocks inside the same action method? And what better approach I can follow?
Separate error cases are certainly not a bad pracice.
What is bad practice, though, is catching unspecific errors. You are returning "AlreadyUsed" for all DbUpdateExceptions. There might be other causes for this than the one you planned for. If that happens you swallow the error and have a silent bug. You might lack the imagination right now what those cases might be but that is just because you never know bugs before they happen. I advise that you catch even more specific than this by either interpreting the exception object (maybe interpret the message, god forbid) or by tightening the region that the catch covers to exactly the statement that can give the error.
In short, don't swallow exceptions indicating bugs. Bugs happen all the time, you want to know about them and fix them.
Also, for the same reason, never ever catch NullReferenceException. They are always bugs by convention. Insert an if already do deal with the null.
If the code in the try-catch is a "logical-entity", e.g. does an independent functionality that wouldnt affect the remaining code, or it wouldnt cause incorrect logic (incorrect execution) to the following code if it had an error. Then Why not.
But if it would break your program logic then it should be stopped and the error should be handled (scope of your try-catch block. it all depends on your program logic.
There is nothing wrong with limiting the try-catch scope(s) to as little as you possibly know.
(I might get flamed for this.)

ServiceStack authentication request fails

I am trying to set up authentication with my ServiceStack service by following this tutorial.
My service is decorated with the [Authenticate] attribute.
My AppHost looks like this:
public class TestAppHost : AppHostHttpListenerBase
{
public TestAppHost() : base("TestService", typeof(TestService).Assembly) { }
public static void ConfigureAppHost(IAppHost host, Container container)
{
try
{
// Set JSON web services to return idiomatic JSON camelCase properties.
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Configure the IOC container
IoC.Configure(container);
// Configure ServiceStack authentication to use our custom authentication providers.
var appSettings = new AppSettings();
host.Plugins.Add(new AuthFeature(() =>
new AuthUserSession(), // use ServiceStack's session class but fill it with our own data using our own auth service provider
new IAuthProvider[] {
new UserCredentialsAuthProvider(appSettings)
}));
}
}
where UserCredentialsAuthProvider is my custom credentials provider:
public class UserCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
try
{
// Authenticate the user.
var userRepo = authService.TryResolve<IUserRepository>();
var user = userRepo.Authenticate(userName, password);
// Populate session properties.
var session = authService.GetSession();
session.IsAuthenticated = true;
session.CreatedAt = DateTime.UtcNow;
session.DisplayName = user.FullName;
session.UserAuthName = session.UserName = user.Username;
session.UserAuthId = user.ID.ToString();
}
catch (Exception ex)
{
// ... Log exception ...
return false;
}
return true;
}
}
In my user tests I initialize and start my TestAppHost on http://127.0.0.1:8888, then use JsonServiceClient to authenticate itself to the service like so:
var client = new JsonServiceClient("http://127.0.0.1:8888/")
var response = client.Send<AuthResponse>(new Auth
{
provider = UserCredentialsAuthProvider.Name,
UserName = username,
Password = password,
RememberMe = true
});
But getting the following exception:
The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](Object request)...
The ServiceStack.ServiceInterface.Auth.Auth request contains the correct username and passsword, and the request is being posted to:
http://127.0.0.1:8888/json/syncreply/Auth
I am not sure why the URL is not /json/auth/credentials or what I might be doing wrong. Any suggestions?
UPDATE
Tracing the chain of events up the stack I found the following:
JsonDataContractSerializer.SerializeToStream correctly serializes the Auth request into Json. However, the System.Net.HttpRequestStream passed to JsonDataContractDeserializer by EndpointHandlerBase has a stream of the correct length that is filled with nulls (zero bytes). As a result, the request object passed to CredentialsAuthProvider.Authenticate has nulls in all its properties.
How can the HTTP stream get stripped of its data?
Got it!!!
The problem was the following pre-request filter that I added for logging purposes in TestAppHost.Configure:
PreRequestFilters.Add((httpReq, httpRes) =>
{
LastRequestBody = httpReq.GetRawBody();
});
as seen here.
When the GetRawBody() method reads the request InputStream it leaves it in the EOS state, and all subsequent read attempts return nothing.
So obviously GetRawBody() can only be safely used with buffered streams, but unfortunately it quietly causes a very nasty bug instead of throwing an exception when used with a non-buffered stream.

Using Redemption, How can I add a default Signature to an outgoing Email Message?

Here is the code that I am using. I have spent some time looking at the Redemption objects, but, nothing jumps out at me:
public static bool PopEmail(string domainUserName, string mSubject, string mBody, string mTo, string mCc = "", string mBcc = "", List<String> fileAttachments = null)
{
log.Info("Starting to Pop Outlook Email Message");
RDOSession oSession = new RDOSession();
try
{
oSession.LogonExchangeMailbox(domainUserName, string.Empty);
if (oSession.LoggedOn)
{
RDOMail oMail = oSession.GetDefaultFolder(rdoDefaultFolders.olFolderOutbox).Items.Add("IPM.Note");
oMail.Subject = mSubject;
oMail.Body = mBody;
oMail.To = mTo;
oMail.CC = mCc;
oMail.BCC = mBcc;
if (fileAttachments != null)
{
foreach (string file in fileAttachments)
{
object newFile = file;
oMail.Attachments.Add(newFile, Type.Missing, Type.Missing, Type.Missing);
newFile = null;
}
}
oMail.Display();
Marshal.FinalReleaseComObject(oMail);
oMail = null;
}
oSession.Logoff();
Marshal.FinalReleaseComObject(oSession);
oSession = null;
GC.Collect();
GC.WaitForPendingFinalizers();
log.Info("Outlook Email has been Popped.");
return true;
}
catch (Exception)
{
log.Error("Outlook Pop Email Failed.");
throw;
}
}
Thank you,
The signature is actually inserted by the Outlook inspector object on instantiation, so if your code is running inside an Outlook addin you could probably try saving the item and then reopening it from the OOM as a _MailItem via _Namespace.GetItemFromId and then calling its GetInspector method (you don't actually have to do anything with the returned inspector reference).
Note that I haven't tried this with an item initially created via RDO. I usually create the items in OOM and then create an RDO wrapper.
If your code is running outside of Outlook you'd have to use OLE to get a reference to its _Application object and then pull the _Namespace object from there. If you are using standalone MAPI without Outlook installed the signature functionality is completely unavailable.
I have added code to append to the oMail.HTMLBody which reads the signature from the C:\Users\UserName\AppData\Roaming\Microsoft\Signatures folder. This file is generated via a plug in written by one of our developers that reads information from Exchange to determine User Name, Title, Phone, Fax, etc.

Resources