user8128167
user8128167

Reputation: 7676

ASP.NET Web API keeps returning the same results even with different input

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?

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:

enter image description here

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:

http://myserver.westus2.cloudapp.azure.com/EddiToolsAPI/api/calculations?username=mycompany&password=mypassword&latitude=34.123478&longitude=115.123478&depth=2&corporationId=2/

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;
    }

enter image description here

Upvotes: 1

Views: 1803

Answers (2)

user8128167
user8128167

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

Andrei Dragotoniu
Andrei Dragotoniu

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:

http://localhost:6600/api/calculations?username=mycompany&password=mypassword&latitude=34.123478&longitude=115.123478&depth=2&corporationId=15

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

Related Questions