1

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

user8128167
  • 6,929
  • 6
  • 66
  • 79
  • 1
    Are you clearing the browser cache also? – Kenneth K. Aug 01 '19 at 19:10
  • 1
    Yes, I choose clear Browsing history, Download history, cookies and other site dat, and cached images and files for the last hour. – user8128167 Aug 01 '19 at 19:12
  • What values do you observe in the controller's return if you debug it? Are they correct, or is the controller even being hit? – Kenneth K. Aug 01 '19 at 19:13
  • 1
    That's the problem, I'm not even sure how to debug functions in the controller in the Web API app. If I try to set a breakpoint it never stops there. The only way I've found to really debug a Web API function is to use Fiddler with Postman: https://www.c-sharpcorner.com/article/how-to-use-fiddler-with-asp-net-web-api-testing/ – user8128167 Aug 01 '19 at 19:15
  • 2
    Fiddler, Postman, and the browser will all work the same in this regard: sending a request to the API. If the controller is not being hit, then make sure you're actually running in debug mode--you might have IIS express down in the tray, but not actually running the project under debug in VS. Also, make sure the port number you're using is correct in both places (i.e. Fiddler and the browser). – Kenneth K. Aug 01 '19 at 19:17

2 Answers2

2

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.

Andrei Dragotoniu
  • 6,155
  • 3
  • 18
  • 32
0

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");
        }
    }
user8128167
  • 6,929
  • 6
  • 66
  • 79