Reputation: 7676
Please note that I have a math library that performs calculations, and when I unit test the library the values returned are correct.
Then I call this library from a function in my Web API application:
private readonly ICalcContext _context;
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private readonly MemoryCache _memCache = MemoryCache.Default;
public CalcRepository(ICalcContext context)
{
_context = context;
}
public async Task<double[]> GetCalculationAsync(string username, string password, int corporationId, double latitude,
double longitude, double depth, bool includeWellRuns = false)
{
double[] results = null;
Calculation calc = _context.Calculations.FirstOrDefault(e => e.CorporationId == corporationId);
if (calc == null) calc = new Calculation();
var user = _context.MyCalcs.FirstOrDefault(e => e.UserName == username);
if (user?.LicenseKey == password)
{
results = MyMathLibrary.MyCalculationFunction(latitude, longitude, depth, DateTime.Now.Day,
DateTime.Now.Month, DateTime.Now.Year, DateTime.Now.Year, calc.UseGeoid, calc.UseDecimalYear);
calc.Value1 = Convert.ToString(results[0]);
calc.Value2 = Convert.ToString(results[1]);
calc.CorporationId = corporationId;
if (String.IsNullOrEmpty(calc.Username)) calc.Username = username;
if (String.IsNullOrEmpty(calc.Password)) calc.Username = password;
_context.Calculations.AddOrUpdate(calc);
await SaveChangesAsync();
CacheHandling(calc);
}
return results;
}
private void CacheHandling(Calculation calc)
{
var res = _memCache.Get("calc");
if (res != null)
{
//This is to remove the MemoryCache - start
if (_memCache.Contains("calc"))
{
_memCache.Remove("calc");
}
}
else
{
_memCache.Add("calc", calc, DateTimeOffset.UtcNow.AddSeconds(30));
}
}
Here is my controller function:
[Route("{username}/{password}/{latitude}/{longitude}/{depth}/{corporationId}")]
public async Task<IHttpActionResult> Get(string username, double latitude,
double longitude, double depth, int corporationId)
{
try
{
var result = await _repository.GetCalculationAsync(username, password, corporationId, latitude, longitude, depth);
if (result == null) return NotFound();
// Mapping
// This centralizes all of the config
var mappedResult = _mapper.Map<IEnumerable<CalculationModel>>(result);
return Ok(mappedResult);
}
catch (Exception ex)
{
Logger.Error(ex);
// Be careful about returning exception here
return InternalServerError();
}
}
The problem is that every time I call the Web API function through the web browser client, the same values show up in my web browser.
Here is one sample input URL: http://localhost:6600/api/calculations/mycompany&mypassword&34.123478&-115.123478&2/
Then here is a second URL I send which should cause new values to show up in the web browser client, but doesn't for some reason:
http://localhost:6600/api/calculations/mycompany&mypassword&10.123478&-100.123478&2/
I have even tried clearing the cache, but if I run the function call with different input values it returns the same values to the web client.
The time it worked to give the new updated values is when I changed form using Chrome as the web browser client to using Firefox.
Here are some websites I have checked but am not sure how I can apply this to my situation:
How to clear MemoryCache in ASP.NET Core?
https://www.codeproject.com/Articles/1087902/Caching-in-Web-API
What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?
How to reset serialization after each web api call : C#
So could anyone please explain why the WebAPI/web client are showing the same values, even when I clear the cache? Do I need to clear something in "IIS Express" somehow? What am I missing? This is basically the first Web API I have ever built. TIA.
UPDATE:
Thanks for the suggestions @Kenneth K.
This is what I see in Postman so it looks like the controller is being hit:
UPDATE 2:
Thanks for the suggestion, @Andrei Dragotoniu
Please note that I have now published the Web API to the IIS on the virtual machine. When I call it, I use this URL with the parameter names as you suggested and in Postman it appears to work:
Yes, of course I can use the headers to store the password once I get the basic functionality working. According to Postman, it appears to connect, but I've have changed the CalcController to add a new record every time it is called by changing to _context.Calculations.Add(calc)
, and even when I use Postman to execute the URL it doesn't appear to actually execute this _context.Calculations.Add(calc)
portion because I see no new records added to the database, so I'm not sure what to do next. Any ideas?
public async Task<double[]> GetCalculationAsync(string username, string password, int corporationId, double latitude,
double longitude, double depth, bool includeWellRuns = false)
{
double[] results = null;
try
{
var calc = new Calculation();
var user = _context.MyCalcs.FirstOrDefault(e => e.UserName == username);
if (user?.LicenseKey == password)
{
results = MyMathLibrary.MyCalculationFunction(latitude, longitude, depth, DateTime.Now.Day,
DateTime.Now.Month, DateTime.Now.Year, DateTime.Now.Year, calc.UseGeoid, calc.UseDecimalYear);
calc.Declination = Convert.ToString(results[0]);
calc.Inclination = Convert.ToString(results[1]);
calc.TotalField = Convert.ToString(results[2]);
calc.CorporationId = corporationId;
if (String.IsNullOrEmpty(calc.Username)) calc.Username = username;
if (String.IsNullOrEmpty(calc.Password)) calc.Password = password;
_context.Calculations.Add(calc);
await SaveChangesAsync();
//CacheHandling(calc);
}
}
catch (Exception ex)
{
Logger.Error(ex);
throw;
}
return results;
}
Upvotes: 1
Views: 1803
Reputation: 7676
Please note that I found that I had missed a class for dependency injection, so I added this to ConfigureServices:
services.AddScoped<IHRGMRepository, HRGMUserRepository>();
Because I was getting this error:
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'EddiTools.Data.IHRGMRepository' while attempting to activate 'EddiTools.Controllers.CalculationsController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
Stack Query Cookies Headers
InvalidOperationException: Unable to resolve service for type 'EddiTools.Data.IHRGMRepository' while attempting to activate 'EddiTools.Controllers.CalculationsController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
lambda_method(Closure , IServiceProvider , object[] )
Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider+<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider+<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Also, I was getting this error because I had to add automapper to configureservices:
services.AddAutoMapper(typeof(Startup));
And this attribute to my controller ctor:
[ActivatorUtilitiesConstructor]
public CalculationsController(ICalcRepository repository, IHRGMRepository hrgmRepository, IMapper mapper)
{
_repository = repository;
_hrgmRepository = hrgmRepository;
_mapper = mapper;
}
Because I was getting these errors:
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'AutoMapper.IMapper' while attempting to activate 'EddiTools.Controllers.CalculationsController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
Stack Query Cookies Headers
InvalidOperationException: Unable to resolve service for type 'AutoMapper.IMapper' while attempting to activate 'EddiTools.Controllers.CalculationsController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
lambda_method(Closure , IServiceProvider , object[] )
Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider+<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider+<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
And,
Severity Code Description Project File Line Suppression State
Error CS0121 The call is ambiguous between the following methods or properties: 'ServiceCollectionExtensions.AddAutoMapper(IServiceCollection, params Assembly[])' and 'ServiceCollectionExtensions.AddAutoMapper(IServiceCollection, params Type[])' MyApps.EddiToolsAPI C:\Projects\eddi\MyApp.EddiToolsAPI\Startup.cs 35 Active
An unhandled exception occurred while processing the request.
InvalidOperationException: No constructor for type 'AutoMapper.Mapper' can be instantiated using services from the service container and default values.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain)
Stack Query Cookies Headers
InvalidOperationException: No constructor for type 'AutoMapper.Mapper' can be instantiated using services from the service container and default values.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceTypNe, CallSiteChain callSiteChain)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
lambda_method(Closure , IServiceProvider , object[] )
Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider+<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider+<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
For details, please see Trying to add AutoMapper to Asp.net Core 2?
and
ASP.NET Core Dependency Injection with Multiple Constructors
UPDATE:
Please note that I also found that I needed to change from a GET method to a POST method. When I did that then the call to the Web API call actually hit the database.
[AllowAnonymous]
[HttpPost("authenticate")]
[Route("{username}/{latitude}/{longitude}/{depth}/{corporationId}")]
public async Task<IActionResult> Authenticate(string username, double latitude, double longitude, double depth, int corporationId)
{
try
{
// ...
var result = await _repository.GetCalculationAsync(username, password, corporationId, latitude, longitude, depth);
if (result == null) return Unauthorized();
// Mapping
// This centralizes all of the config
var mappedResult = _mapper.Map<IEnumerable<CalculationModel>>(result);
return Ok(mappedResult);
}
catch (Exception ex)
{
Logger.Error(ex);
return this.StatusCode(StatusCodes.Status500InternalServerError, "Database Failure");
}
}
Upvotes: 0
Reputation: 6335
First of all sort out your controller as it's a huge mess, your route does not even match the parameters:
[Route("{username}/{password}/{latitude}/{longitude}/{depth}/{corporationId}")]
public async Task<IHttpActionResult> Get(
string username,
string password,
double latitude,
double longitude,
double depth,
int corporationId)
Second fix your calling URL:
http://localhost:6600/api/calculations/mycompany/mypassword/34.123478/115.123478/2/15
you have 6 parameters, make sure your route / params / calling url all match up.
If I were you, I would not pass username / password in the URL, that's a huge security risk, pass them in the authorization header for example, or find another secure method to deal with this issue.
Now, because you have a lot of parameters, I would probably replace them with a model.
If you still can't debug inside the controller, then go back to the basics, change your calling URL to something like this:
you might even want to URL encode the double values, since they contains dots, just to eliminate that as a potential problem. Unfortunately I can't check now to see if this is a problem.
Upvotes: 2