Using .NET Core 1.1, I've created an AuthorizationHandler:
public class HasStoreAccountAuthorizationHandler : AuthorizationHandler<HasStoreAccountRequirement>
{
private SessionHelper SessionHelper { get; }
public HasStoreAccountAuthorizationHandler(SessionHelper sessionHelper)
{
this.SessionHelper = sessionHelper;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasStoreAccountRequirement requirement)
{
if (this.SessionHelper?.Accounts != null && this.SessionHelper.Accounts.Any()) context.Succeed(requirement);
return Task.FromResult(0);
}
}
And I have a SessionHelper class which serializes non-primitive values into session:
public class SessionHelper
{
public IEnumerable<AccountInformation> Accounts
{
get => this.Session.Get<IEnumerable<AccountInformation>>(AccountsKey);
set => this.Session.Set<IEnumerable<AccountInformation>>(AccountsKey, value);
}
public static class SessionHelperExtensionMethods
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
}
}
}
Accessing SessionHelper.Accounts anywhere else in the code works fine. However, whenever the policy AuthorizationHandler is called, the following error is thrown:
An exception was thrown while deserializing the token.
System.InvalidOperationException: The antiforgery token could not be decrypted.
---> System.Security.Cryptography.CryptographicException: The key {KEYNAME} was not found in the key ring.
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData) at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken) --- End of inner exception stack trace --- at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken) at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.GetCookieTokenDoesNotThrow(HttpContext httpContext) System.Security.Cryptography.CryptographicException: The key {KEYNAME} was not found in the key ring.
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData) at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken)
The web site machine key is set in the web.config and IIS,using SHA1/AES with both "Generate a unique key for each application" checked and unchecked (e.g., "KEY_VALUE,IsolateApps" and "KEY_VALUE").
I've also created a data protection registry hive and configured the IIS app pool to load the user profile per this link.
I've also added data protection in Startup.cs (with various lines added/removed):
services.AddAntiforgery();
services.AddDataProtection()
.SetApplicationName("APPNAME")
.SetDefaultKeyLifetime(TimeSpan.FromDays(365))
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["DataProtection:LocalPaths:KeyLocation"]));
In some cases, SessionHelper.Accounts is read correctly and no exception is thrown. However, once the app recycles, eventually SessionHelper.Accounts is null and the exception is thrown. Logging out of the system and logging in again has no effect on error.
Thoughts?
I've managed to fix this by accessing the session data via the passed AuthorizationHandlerContext context instead of the IHttpContextAccessor context.
In SessionHelper:
public static IEnumerable<AccountInformation> GetAccounts(HttpContext context)
{
return context.Session.Get<IEnumerable<AccountInformation>>(AccountsKey);
}
In the AuthorizationHandler:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasStoreAccountRequirement requirement)
{
var accounts = SessionHelper.GetAccounts(this.Context.HttpContext);
if (accounts != null && accounts.Any()) context.Succeed(requirement);
return Task.FromResult(0);
}
Related
After about 15 hours of troubleshooting, I am just so confused at what is happening, and no longer sure what the real issue is, so I plead for any suggestions or hints you may have.
I have a ASP.NET Core project using .NET 5, EntityFramework Core, Text.Json and Nullable Reference Types. When my models have a parameterless constructor, everything seems to work fine, but if I try to create a parameterized constructor I start having issues.
When requesting a list of entries, it seems fine, but request a single entry returns an ArgumentNullException. The problem seems to come from a lack of parameterless constructor, but this introduces problems with the nonnullable properties.
After having tried to isolate the problem, it still appears when all navigational properties is removed, so the problem appears in the following basic application.
As Id, Name and IsComplete are nonnullable, I understand that they should be in a parameterized constructor, as mentioned in the docs:
When nullable reference types are enabled, the C# compiler emits warnings for any uninitialized non-nullable property, as these would contain null. As a result, the following, common way of writing entity types cannot be used:
public class CustomerWithWarning
{
public int Id { get; set; }
// Generates CS8618, uninitialized non-nullable property:
public string Name { get; set; }
}
Constructor binding is a useful technique to ensure that your non-nullable properties are initialized:
public class CustomerWithConstructorBinding
{
public int Id { get; set; }
public string Name { get; set; }
public CustomerWithConstructorBinding(string name)
{
Name = name;
}
}
Following is included most of the (relevant) code:
Error-message and stack-trace at bottom:
TodoItem.cs (not working):
using System.Text.Json.Serialization;
namespace TodoApi.Models
{
public class TodoItem
{
[JsonConstructor]
public TodoItem(long id, string name, bool isComplete)
{
(Id, Name, IsComplete) = (id, name, isComplete);
}
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
TodoItem.cs (working, but undesirable):
using System.Text.Json.Serialization;
namespace TodoApi.Models
{
public class TodoItem
{
publicTodoItem()
{
}
[JsonConstructor]
public TodoItem(long id, string name, bool isComplete)
{
(Id, Name, IsComplete) = (id, name, isComplete);
}
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
TodoItemsController.cs (auto-generated):
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
}
}
TodoContext.cs:
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
}
Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Text.Json.Serialization;
using TodoApi.Models;
namespace TodoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMvc().AddJsonOptions(
opt => opt.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve
);
services.AddDbContext<TodoContext>(opt =>
{
opt.UseNpgsql(string.Format(
"host={0};port={1};database={2};username={3};password={4}",
"localhost",
"5432",
"databaseNameHere",
"databaseUsername",
"databasePassword"));
opt.UseLazyLoadingProxies();
}
);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Error-message:
An unhandled exception occurred while processing the request.
ArgumentNullException: Value cannot be null. (Parameter 'obj')
System.OrdinalIgnoreCaseComparer.GetHashCode(string obj)
Stack-trace:
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.ArgumentNullException: Value cannot be null. (Parameter 'obj')
at System.OrdinalIgnoreCaseComparer.GetHashCode(String obj)
at System.Text.Json.JsonClassInfo.ParameterLookupKey.GetHashCode()
at System.Collections.Generic.Dictionary2.FindValue(TKey key) at System.Collections.Generic.Dictionary2.TryGetValue(TKey key, TValue& value)
at System.Text.Json.JsonClassInfo.InitializeConstructorParameters(ConstructorInfo constructorInfo)
at System.Text.Json.JsonClassInfo..ctor(Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializerOptions.GetOrAddClass(Type type)
at System.Text.Json.JsonSerializerOptions.GetOrAddClassForRootType(Type type)
at System.Text.Json.WriteStack.Initialize(Type type, JsonSerializerOptions options, Boolean supportContinuation)
at System.Text.Json.JsonSerializer.WriteAsyncCore[TValue](Stream utf8Json, TValue value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isComple
ted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
If there's any missing details, please let me know! I've tried to include everything I can think of as relevant.
I have a webhook api handler for Shopify which calls the below controller action with a json body. It is failing immediately because no log4net logging is being reached and logged except the one in the OnException method with the error below.
Question 1:
The Stack Trace in the Elmah log is not helpful in that it doesn't go down far enough to show which line in the code originated the error. Why is this? I've noticed this with async errors...they seem harder to determine the root cause line in the code. Maybe I should make it a synchronous method for now? Maybe I should just get rid of the OnException method as it could be obscuring more info?
Question 2:
What could possibly causing this error immediately upon hitting the controller action before any code is executed? This controller inherits the asp.net mvc Controller and the only code in the constructor is to create an instance of the DBContext and the log4net _logger.
Stack Trace:
Controllers.ShopWebhooksController.OnException(C:\inetpub\wwwroot\Controllers\ShopWebhooksController.cs:44)
System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)
at System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ControllerBase.get_ValueProvider()
at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_1.<BeginInvokeAction>b__0(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state)
at System.Web.Mvc.Controller.<>c.<BeginExecuteCore>b__152_0(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.<>c.<BeginProcessRequest>b__20_0(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.<>c__DisplayClass285_0.<ExecuteStepImpl>b__0()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Here's the Controller, the OrderUpdate is the Action being called:
public class ShopWebhooksController : Controller
{
private readonly ILog _logger;
private readonly InventoryMgmtContext _dbContext;
public ShopWebhooksController()
{
_logger = LogManager.GetLogger(GetType());
_dbContext = new InventoryMgmtContext();
}
protected override void OnException(ExceptionContext filterContext)
{
Exception ex = filterContext.Exception;
var action = filterContext.RouteData.Values["action"];
// TODO: Log or report your exception.
string msg = $"Exception in shopify webhook controller action: {action}. Message: {ex.Message}. Stack: {ex.StackTrace}.";
_logger.Error(msg); **<---- this is being logged**
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.OK, msg);
//Let the base controller finish this execution
base.OnException(filterContext);
}
[HttpPost]
public async Task<ActionResult> OrderUpdated (int storefrontId)
{
string msg = "Successfully submitted update request to Mozzo.";
string webhook = "orders/updated";
_logger.Debug($"Shopify {webhook} request received."); **<-- not being logged**
try
{
var validationResult = await ValidateStorefrontWebhook(webhook, storefrontId);
if (!validationResult.WasSuccessful) return new HttpStatusCodeResult(HttpStatusCode.OK, validationResult.Message);
var orderSyncAppServ = new SyncErpWithPlacedOrdersTask();
Hangfire.BackgroundJob.Enqueue(() => orderSyncAppServ.UpdateOrderFromWebhook(validationResult.Value, storefrontId));
}
catch (Exception e)
{
msg = $"Exception webhook: {webhook} for storefront Id: {storefrontId}. {e.Message}.";
_logger.Error(msg);
}
return new HttpStatusCodeResult(HttpStatusCode.OK, msg);
}
#endregion
#region Private Methods
/// <summary>
/// Validates the webhook is authentic and returns the body of the request as a string
/// </summary>
/// <param name="webhook"></param>
/// <param name="storefrontId"></param>
/// <returns>request body (string version of an order, etc.</returns>
private async Task<ActionConfirmation<string>> ValidateStorefrontWebhook(string webhook, int storefrontId)
{
string returnMessage = "";
//log request
//get the request body (a json string of an order, product, etc coming from shopify.
string jsonObject = await GetRequestBody();
//wrap in brackets to make it an array of one because our import takes an array or orders
jsonObject = $"[ {jsonObject} ]";
//get storefront
var storefront = await _dbContext.StoreFronts.Where(s => s.Id == storefrontId).SingleOrDefaultAsync();
if (storefront == null) {
returnMessage = $"Shopify {webhook} webhook request for Storefront Id: {storefront.Id} - storefront not found!";
_logger.Error($"{LogHelper.GetCurrentMethodName()}: {returnMessage}");
return ActionConfirmation<string>.CreateFailureConfirmation(returnMessage, "", false);
}
log4net.LogicalThreadContext.Properties["AccountId"] = storefront.Company.AccountId;
log4net.LogicalThreadContext.Properties["CompanyId"] = storefront.CompanyId;
log4net.LogicalThreadContext.Properties["FacilityId"] = null;
log4net.LogicalThreadContext.Properties["UserId"] = null;
string shopDomain = storefront.APIUrl;
string shopSecretKey = storefront.StoreFrontTypeId == (int)StoreFront.StoreFrontTypes.ShopifyPrivate
? storefront.AccessToken
: AppSettings.ShopifySecretKey;
_logger.Debug("About to check if webhook is authentic");
var isValidRequest = await AuthorizationService.IsAuthenticWebhook(
Request.Headers.ToKvps(),
Request.InputStream,
shopSecretKey);
if (!isValidRequest)
{
returnMessage = $"Shopify {webhook} webhook request for Storefront Id: {storefront.Id} is not authentic!";
_logger.Error($"{LogHelper.GetCurrentMethodName()}: {returnMessage}");
return ActionConfirmation<string>.CreateFailureConfirmation(returnMessage, "", false);
}
returnMessage = $"Shopify {webhook} webhook request for Storefront Id: {storefront.Id} is authentic!";
_logger.Info($"{LogHelper.GetCurrentMethodName()}: {returnMessage}");
return ActionConfirmation<string>.CreateSuccessConfirmation(returnMessage, jsonObject, false);
}
private async Task<string> GetRequestBody()
{
_logger.Debug($"{LogHelper.GetCurrentMethodName()}: Attempting to get request body.");
//ShopifySharp has just read the input stream. We must always reset the inputstream
//before reading it again.
Request.InputStream.Position = 0;
//Do not dispose the StreamReader or input stream. The controller will do that itself.
string bodyText = await new StreamReader(Request.InputStream).ReadToEndAsync();
_logger.Debug($"{LogHelper.GetCurrentMethodName()}: Request body: {bodyText}.");
return bodyText;
}
#endregion
UPDATE - Problem and Solution
The issue was indeed that the Shopify Order webhook JSON object contained duplicate keys in that they has a lowercase and TitleCase version of 4 keys in the same object wrapper.
The full path of these keys are:
order,refunds,0,transactions,0,receipt,version
order,refunds,0,transactions,0,receipt,timestamp
order,refunds,0,transactions,0,receipt,ack
order,refunds,0,transactions,0,receipt,build
And the exact code change I made was as below. I did follow the answer provided below on adding my own JsonValueProviderFactory class, but what was not provided, was the exact change to make...because it depends on how you want to handle it. In my case, this change results in any subsequent keys of the same name being thrown away. So if you want to handle it differently, you'd need to address as you desire:
/// <summary>
/// Modified this to handle duplicate keys
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(string key, object value)
{
if (++_itemCount > _maximumDepth)
{
throw new InvalidOperationException("The JSON request was too large to be deserialized.");
}
// Add the following if block so if the key already exists, just return instead of trying to add it to the dictionary which will throw an error.
if (_innerDictionary.ContainsKey(key))
{
return;
}
_innerDictionary.Add(key, value);
}
I think there is nothing wrong with your design, but one of your class may have duplicated property which will cause runtime exception.
for example
public int storefrontId {get; set;}
public int StorefrontId {get; set;}
And you need to configure log4net to log your action calls.
for ex:
2021-02-16 10:24:17.5632|2|INFO|Microsoft.AspNetCore.Hosting.Diagnostics|Request finished in 141.7419ms 200 |url: http://myapp/OrderUpdated|action:
EDIT
Here is how you can do request log using DelegatingHandler
public class RequestLogHandler : DelegatingHandler
{
private static readonly ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
string requestBody = await request.Content.ReadAsStringAsync();
log.Info($"url {request.RequestUri} body = {requestBody}");
}
//// let other handlers process the request
var result = await base.SendAsync(request, cancellationToken);
return result;
}
}
Register handler in config
config.MessageHandlers.Add(new RequestLogHandler());
This will give you something like below.
Furthermore, I will tell steps to override JsonValueProviderFactory AddToBackingStore method. You use that for find what property causing this issue.
Get source code from here.
Add Class MyJsonValueProviderFactory.cs
Register your new class before JsonValueProviderFactoruy in Global.asax.cs
ValueProviderFactories.Factories.Insert(0, new MyJsonValueProviderFactory());
or remove original first and use yours.
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());
Play with this class with exception catching, you will be able to find where is the problem, you may start with Add method in EntryLimitedDictionary class.
Again use below link to register error handling globally.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling
I am not sure that I understand you correct, but try temporary wraps background call in additional method with logging and try catch:
BackgroundJob.Enqueue(() => UpdateOrderFromWebhookWithLogging(_logger, validationResult.Value, storefrontId));
And add this method to your controller:
// I don't know types to write correct signature
private void UpdateOrderFromWebhookWithLogging(_logger, orderSyncAppServ, validationResult.Value, storefrontId)
{
try
{
orderSyncAppServ.UpdateOrderFromWebhook(validationResult.Value, storefrontId)
}
catch (Exception ex)
{
_logger.Error(ex);
throw;
}
}
It looks like JsonValueProviderFactory.AddToBackingStore is traversing the JSON input and putting each leaf value into a dictionary. The key for the dictionary is the path to the leaf node. That exception would occur if the traversal encounters two leaf nodes with the same path.
I think you need to check the JSON input data - perhaps it has duplicate keys. E.g. this is valid JSON:
{
"id": 1,
"name": "Some Name"
}
whereas this is not:
{
"id": 1,
"name": "Some Name",
"id": 2
}
because the "id" key appears more than once. This might cause the error you're seeing.
I have an app running in a GCP cloud run instance, and it uses EF Core mapped on a pgsql db, but I can't connect to the database for some reason
Here's the code used to connect to the database :
Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<postgresContext>(options =>
options.UseNpgsql(SecretManagerAccessor.GetConnectionString())
);
}
Controller :
public MyController(IConfiguration configuration, postgresContext context, IMapper mapper)
{
_configuration = configuration;
_context = context;
_mapper = mapper;
}
[HttpPost]
public IActionResult Post([FromBody] Body body)
{
try
{
var repo = new SourceRepository(_context);
var sources = repo.GetAll();
foreach (var source in sources)
{
Console.WriteLine($"{source.SrcId} : {source.SrcInfo}");
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
return Ok();
}
And finally SourceRepository :
private readonly postgresContext _db;
private readonly DbSet<Source> _source;
public SourceRepository(postgresContext db)
{
_db = db;
_source = db.Set<Source>();
}
public IEnumerable<Source> GetAll()
{
return _source;
}
I tried 2 types of connection string :
Server=xx.xx.xxx.xx;Port=5432;Database=MyDb;Username=username;Password=pass using this website
and
Host=xx.xx.xxx.xx;Database=MyDb;Username=username;Password=pass using this documentation
The strings are hosted in GCP's Secret Manager, and I used a Database First approach to generated the store objects and the context.
The Cloud Run service account has a "Cloud SQL Admin" permission (even if it should work with Client level).
The error I get is this :
A 2020-08-26T13:58:49.877242Z An error occurred using the connection to database 'MyDb' on server 'tcp://xx.xx.xxx.xxx:5432'.
A 2020-08-26T13:58:49.877242Z System.InvalidOperationException: An exception has been raised that is likely due to a transient failure.
A 2020-08-26T13:58:49.877286Z ---> Npgsql.NpgsqlException (0x80004005): Exception while connecting
A 2020-08-26T13:58:49.877295Z ---> System.TimeoutException: Timeout during connection attempt
A 2020-08-26T13:58:49.877485Z at Npgsql.NpgsqlConnector.Connect(NpgsqlTimeout timeout)
A 2020-08-26T13:58:49.877514Z at Npgsql.NpgsqlConnector.Connect(NpgsqlTimeout timeout)
A 2020-08-26T13:58:49.877528Z at Npgsql.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
A 2020-08-26T13:58:49.877540Z at Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
A 2020-08-26T13:58:49.877553Z at Npgsql.ConnectorPool.AllocateLong(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
A 2020-08-26T13:58:49.878103Z at Npgsql.NpgsqlConnection.<>c__DisplayClass32_0.<<Open>g__OpenLong|0>d.MoveNext()
A 2020-08-26T13:58:49.878128Z --- End of stack trace from previous location where exception was thrown ---
A 2020-08-26T13:58:49.878136Z at Npgsql.NpgsqlConnection.Open()
A 2020-08-26T13:58:49.878145Z at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnection(Boolean errorsExpected)
A 2020-08-26T13:58:49.878152Z at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Open(Boolean errorsExpected)
A 2020-08-26T13:58:49.878161Z at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)
A 2020-08-26T13:58:49.878170Z at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.InitializeReader(DbContext _, Boolean result)
A 2020-08-26T13:58:49.878180Z at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
A 2020-08-26T13:58:49.878188Z --- End of inner exception stack trace ---
A 2020-08-26T13:58:49.878197Z at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
A 2020-08-26T13:58:49.878203Z at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
A 2020-08-26T13:58:49.878211Z at MyProject.Controllers.MyController.Post(Body body) in /app/Controllers/MyController.cs:line 62
I simplified the test by using this code instead, still have the same issue though :
//GET : api/MyController
[HttpGet]
public IActionResult Get()
{
Console.WriteLine("Get route called");
try
{
_context.Database.CanConnect();
}
catch (Exception e)
{
Console.WriteLine(e);
}
return Ok();
}
You can use flag --add-cloudsql-instances when creating instance. Cloud Run will activate and configure the Cloud SQL proxy for you.
Connection string should look similar to this one
"Server=/cloudsql/your-project-id:us-central1:instance-name;Uid=aspnetuser;Pwd=;Database=votes"
You can take a look at the documentation
There is also a repo showing how to build C# application on Cloud Run with Postgres repo
I'm trying to integrate a hash into my database, which is generated by a POST request when creating a new user.
The APIs being new to me, I don't really understand the error and why my code failed. Is someone can explain to me what I am doing wrong and how I could make this work ?
Here's the code I wrote :
Controller
//POST: api/user/
[HttpPost]
public ActionResult<User> PostUserItem(User user)
{
string Hash = RandomNumberGenerator.GetInt32(1000, 9999).ToString();
SHA512 sha512 = SHA512.Create(Hash);
string Hashed = sha512.ToString();
user.Token_Validation = Hashed;
_context.UserItems.Add(user);
_context.SaveChanges();
return CreatedAtAction("GetUserItem", new User{idUser=user.idUser}, user);
}
Model
[Table("USERS")]
public class User
{
[Key]
public int idUser { get; set; }
public string name { get; set; }
public string lastName { get; set; }
public string email { get; set; }
public string pwd { get; set; }
public string Token_Validation { get; set; }
}
DBContext
public class RouteContext : DbContext
{
public RouteContext(DbContextOptions<RouteContext> options) : base(options)
{
}
public DbSet<User> UserItems { get; set; }
}
And here's the ERROR 500 I got when trying to use my API
System.NullReferenceException: Object reference not set to an instance of an object.
at LocalBeers.Controllers.UserController.PostUserItem(User user) in /var/www/html/backend/Controllers/UserController.cs:line 46
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
This is incorrect:
string Hash = RandomNumberGenerator.GetInt32(1000, 9999).ToString();
SHA512 sha512 = SHA512.Create(Hash);
...At this point, sha512 is null and the next line throws NullReferenceException which causes the server to return an HTTP 500 (Internal Server Error) status.
The SHA512.Create(String) overload expects the name of an implementation, not the data you want to hash.
More information here.
Instead, try something like this:
var random = RandomNumberGenerator.GetInt32(1000, 9999).ToString();
string hashed = string.Empty;
using (SHA512 sha512 = SHA512.Create())
{
var hashValue = sha512.ComputeHash(System.Text.Encoding.Default.GetBytes(random));
hashed = System.Convert.ToBase64String(hashValue);
}
user.Token_Validation = hashed;
I am trying to debug my code but I am getting System.NullReferenceException exception just after adding [frombody] to the parameter but when I removed [frombody] and tried again everything worked normaly
This my controller:
public async Task<IActionResult> Register([FromBody] UserForRegisterDTO userForRegisterDTO)
{
//validate request
userForRegisterDTO.Username = userForRegisterDTO.Username.ToLower();
if(await _repo.UserExist(userForRegisterDTO.Username))
return BadRequest("Username is already taken");
}
DTO :
namespace DatingApp.API.DTO
{
public class UserForRegisterDTO
{
public string Username { get; set; }
public string Password { get; set; }
}
}
Postman :
Error:
fail:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware1
An unhandled exception has occurred while executing the request. System.NullReferenceException: Object reference not set to an instance
of an object.
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Solved
Something strange happened. I solved this problem by using "dotnet watch run " instead of "dotnet run "