I am creating a middleware to store all incoming API request and response body along with some other data, I am using Entity Framework as my ORM.
It's working well, but in some request and response the response body is not getting stored.
While I log the error it shows:
Object name: 'TransactionDBContext'.line no 248
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
I just every method using async returns task and await keyword using properly
Middleware.cs:
public class TransactionLoggingMiddleWare
{
private readonly RequestDelegate _next;
private TransactionLogService _transactionLogService;
public TransactionLoggingMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, TransactionLogService transactionLogService)
{
_transactionLogService = transactionLogService;
Guid GUID = Guid.NewGuid();
try
{
var request = context.Request;
if (request.Path.ToString().Contains("Remittance/Post"))
{
var requestTime = DateTime.Now;
var requestBodyContent = await ReadRequestBody(request);
int bcode = requestBodyContent.IndexOf("content-disposition:");
if (bcode > 0)
{
string jung1 = requestBodyContent.Substring(0, bcode - 1);
requestBodyContent = requestBodyContent.Replace(jung1, "");
}
context.Items["Guid"] = GUID;
requestBodyContent = requestBodyContent.Replace("content-disposition: form-data; name=", "");
var form = await context.Request.ReadFormAsync();
var transactionData = new RemittanceRQ();
var properties = typeof(RemittanceRQ).GetProperties();
int remIdValue = 0;
long TxnRefNo = 0;
string productcode = null;
foreach (var property in properties)
{
// Get the property name
var propertyName = property.Name;
// Check if the form data contains the key corresponding to the property name
if (form.ContainsKey(propertyName))
{
// Get the value from the form data
var value = form[propertyName];
// Convert and assign the value to the property
if (value != String.Empty)
{
// Handle different property types accordingly
if (property.PropertyType == typeof(int))
{
property.SetValue(transactionData, Convert.ToInt32(value));
}
else if (property.PropertyType == typeof(Int16))
{
property.SetValue(transactionData , Convert.ToInt16(value));
}
else if (property.PropertyType == typeof(long))
{
property.SetValue(transactionData, long.Parse(value));
}
else if (property.PropertyType == typeof(decimal))
{
property.SetValue(transactionData, decimal.Parse(value));
}
else if (property.PropertyType == typeof(short))
{
property.SetValue(transactionData, Convert.ToInt16(value));
}
else if (property.PropertyType == typeof(String))
{
property.SetValue(transactionData, value.ToString());
}
if (propertyName == "RemID")
{
remIdValue = Convert.ToInt32(value);
}
if (propertyName == "TxnRefNumber")
{
TxnRefNo = long.Parse(value);
}
}
}
}
// to store only request body
await _transactionLogService.AddTransaction(new TransactionLogItem
{
TransactionGuid = GUID.ToString(),
CreatedDate = requestTime,
APIRequest = JsonConvert.SerializeObject(transactionData),
RemId = remIdValue,
TxnNumber = TxnRefNo,
});
// reading the response
var orginalBodyStream = context.Response.Body;
var originalBodyCode = context.Response.StatusCode;
try
{
using (var responseBody = new MemoryStream())
{
//context.Response.Body = responseBody;
var response = context.Response;
response.Body = responseBody;
await _next(context);
responseBody.Seek(0, SeekOrigin.Begin);
var responseBodyContent = await new StreamReader(responseBody).ReadToEndAsync();
var responseData = JsonConvert.DeserializeObject<ResponseData>(responseBodyContent);
if (responseData != null)
{
var txnReferenceNo = responseData.Data?.TxnReferenceNo;
}
await _transactionLogService.UpdateTransactionLog(new TransactionLogItem
{
TransactionGuid = GUID.ToString(),
APIResponse = responseBodyContent,
Status_Code = response.StatusCode,
TxnNumber = responseData.Data?.TxnReferenceNo,
TransactionStatus = responseData.IsSuccess,
XPIN = responseData.Data?.TTNUM,
});
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(orginalBodyStream);
}
// end of reading response body
//await _next(context);
}
catch (Exception ex)
{
await _transactionLogService.UpdateTransactionLog(new TransactionLogItem
{
TransactionGuid = GUID.ToString(),
ExceptionMsg = ex.Message
});
File.AppendAllText("loggerMiddlewareException.txt",ex.Message.ToString()+"line no 231"+$"{GUID}"+"\n");
}
}
else
{
await _next(context);
}
}
catch (Exception ex)
{
bool translogGuid = context.Items.TryGetValue("Guid", out var requestIdObj);
var request = context.Request;
var requestTime = DateTime.Now;
File.AppendAllText("loggerMiddlewareException.txt", ex.Message.ToString() + "line no 248" + "\n");
var requestBodyContent = await ReadRequestBody(request);
await _transactionLogService.UpdateTransactionLog(new TransactionLogItem
{
TransactionGuid = GUID.ToString(),
ExceptionMsg = ex.Message,
});
await _next(context);
}
}
private async Task<string> ReadRequestBody(HttpRequest request)
{
request.EnableBuffering();
var buffer = new byte[Convert.ToInt32(request.ContentLength??0)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body.Seek(0, SeekOrigin.Begin);
return bodyAsText;
}
}
Service register:
services.AddDbContext<TransactionDBContext>(options => options.UseSqlServer(connectionString));
DbContext
public class TransactionDBContext:DbContext{
public TransactionDBContext(DbContextOptions<TransactionDBContext> options):base(options)
{
}
public DbSet<TransactionLogItem> tblt_TransactionLogs { get; set; }}
Serive.cs:
public class TransactionLogService{
private readonly TransactionDBContext _dbContext;
public int TransactionId { get; set; }
public TransactionLogService(TransactionDBContext transactionDBContext)
{
_dbContext = transactionDBContext;
}
public async Task AddTransaction(TransactionLogItem transactionLogItem)
{
_dbContext.tblt_TransactionLogs.Add(transactionLogItem);
await _dbContext.SaveChangesAsync();
//TransactionId = transactionLogItem.TransactionLogID;
}
public async Task UpdateTransactionLog(TransactionLogItem LogItem)
{
var existingLog = await _dbContext.tblt_TransactionLogs.FirstOrDefaultAsync(t => t.TransactionGuid == LogItem.TransactionGuid);
if (existingLog != null)
{
existingLog.EngineCode = LogItem.EngineCode = string.IsNullOrEmpty(LogItem.EngineCode) ? existingLog.EngineCode : LogItem.EngineCode; ;
existingLog.APIResponse = LogItem.APIResponse=string.IsNullOrEmpty(LogItem.APIResponse)? existingLog.APIResponse: LogItem.APIResponse;
existingLog.Status_Code = LogItem.Status_Code==0?existingLog.Status_Code:LogItem.Status_Code;
existingLog.TxnNumber= LogItem.TxnNumber == null || LogItem.TxnNumber == 0 ? existingLog.TxnNumber:LogItem.TxnNumber;
existingLog.XPIN = LogItem.XPIN=string.IsNullOrEmpty(LogItem.XPIN)?existingLog.XPIN:LogItem.XPIN;
existingLog.MTORequest=LogItem.MTORequest=string.IsNullOrEmpty(LogItem.MTORequest)?existingLog.MTORequest:LogItem.MTORequest;
existingLog.MTOResponse=LogItem.MTOResponse=string.IsNullOrEmpty(LogItem.MTOResponse)?existingLog.MTOResponse:LogItem.MTOResponse;
existingLog.ExceptionMsg = LogItem.ExceptionMsg = string.IsNullOrEmpty(LogItem.ExceptionMsg) ? existingLog.ExceptionMsg : LogItem.ExceptionMsg;
existingLog.TransactionStatus = LogItem.TransactionStatus;
}
await _dbContext.SaveChangesAsync();
}
}
It's working fine in almost 80% case successfully but in some case it fails to store only response.
How to fix it ? Issue in production
TransactionLogServiceis singleton, so it keeps the DbContext instance created from the very first request and tries to reuse it. Check Using Scoped Services in a BackgroundService. The problem and solution are the same_transactionLogServiceuses DbContext incorrectly.