Is there a way to use a foreach() to call an API multiple times with different parameters. Currently the code only executes the first item (itemOne) and not the rest of the items. I want to get back all the results and then exit the function.
The method that calls TestExecute() comes from the controller. I haven't shown controller method since it only calls TestLogs. There are no errors being shown. Basically I want to loop through all three of the items to get the itemId and store it in the list. In this case it would call the API 3 times and store the results and only then do I want to exit this function.
public class TestLogs
{
private readonly ITest _test;
public TestLogs()
{
_test = new test();
}
private async Task<TestProjectsDto> GetProjectId()
{
var date = new DateTime(2020, 04, 15);
var sapProjectNum = new TestProjectsDto
{
Projects = new List<TestProjectDto>()
};
var list = new List<string>()
{
"itemOne",
"itemTwo",
"itemThree"
};
foreach (var divList in list)
{
var proIds = await _test.GetProjectItem(divList, date);
if (proIds != null)
{
sapProjectNum.Projects.AddRange(proIds.Projects);
}
}
return sapProjectNum;
}
public async Task TestExecute()
{
// Where I call GetProjectId();
var listProjectIds = GetProjectId();
// etc
}
}
}
public class TestService : ITest
{
//etc
public async Task<TestProjectsDto> GetProjectOnSpecificDate(string divisionName, DateTime date)
{
var url = $"{test}/GetProjectOnSpecificDate.xsjs?TYPE=item={item}&Date={date:yyyy-MM-dd}";
var response = await HttpClient.GetAsync(url).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsAsync<TestProjectsDto>().ConfigureAwait(false);
return content;
}
return null;
}
}
Related
I am using mock library in my .Net unit test and getting an error
cannot be accessed with an instance reference instead use type name.
I am getting this error at following line in my test method where it is calling cq.Instance. I am new to mock library. Could somebody let me know how do I call the static method?
attributeValue.Setup(cq => cq.Instance().CallQueryAsync(request, 1)).Returns(attrValue);
Actual method to be tested
public static async Task<AttributeValueList> GetAttributeSecDateValueList(int attrId)
{
try
{
var request = AttributeValue.ResolveRequest(attrId);
var response = await AsyncProxy<AttributeValue>.Instance().CallQueryAsync(request, (int)AttributeValue.OperationType.GetSecDateValues);
var coll = new AttributeValueList();
coll.AddRange(response);
return coll;
}
catch (Exception e)
{
throw e;
}
}
Proxy class
public class AsyncProxy<RT> : IDisposable
where RT : class, new()
{
readonly WebServiceProxy<RT> _wsProxy;
private AsyncProxy(WebServiceProxy<RT> webServiceProxy)
{
_wsProxy = webServiceProxy;
}
public static async Task<IEnumerable<RT>> Load(object parameters)
{
return await Instance().CallQueryAsync(parameters);
}
public static AsyncProxy<RT> Instance()
{
return new AsyncProxy<RT>(WebServiceProxy<RT>.Instance());
}
/// <summary>
/// Return result set of Poco as smartCollection
/// </summary>
public async Task<SmartCollection<RT>> CallQueryAsync(object request, int? uniqueIdentifier = null, bool isLongRunning = false, [CallerMemberName]string memberName = "")
{
//#if DEBUG
// var stopwatch = new Stopwatch();
// stopwatch.Start();
//#endif
try
{
// We want to get rid of the proxy as soon as we are done with it
using (_wsProxy)
{
var awaited = await _wsProxy.CallQueryAsync(request, uniqueIdentifier, isLongRunning);
if (awaited == null)
return null;
var observableCollection = new SmartCollection<RT>();
foreach (var item in awaited)
observableCollection.Add(item as RT);
return observableCollection;
}
}
finally
{
Dispose();
//#if DEBUG
// stopwatch.Stop();
// Debug.WriteLine(null);
// Debug.WriteLine($"****>>>>> AsyncProxy {memberName} took {stopwatch.ElapsedMilliseconds} ms <<<<<<<<****");
//#endif
}
}
}
test method
[TestMethod]
public void test()
{
Task<SmartCollection<AttributeValue>> attrValue = null;
var request = new AttributeValue();
var attributeValue = new Mock<AsyncProxy<AttributeValue>>();
attributeValue.Setup(cq => cq.Instance().CallQueryAsync(request, 1)).Returns(attrValue);
}
class Website
{
public Website(string link)
{
_linkToWeb = new RestClient(link);
}
public async Task<string> DownloadAsync(string path)
{
var request = new RestRequest(path, Method.GET);
var response = _linkToWeb.ExecuteAsync(request);
return response.Result.Content;
}
public RestClient _linkToWeb { get; set; }
}
class Program
{
public static Website API = new Website("https://api.collegefootballdata.com");
public static async Task<string> _downloadTeamsFromAPI()
{
return API.Download("/teams/fbs");
}
public static async Task<string> _downloadAdvancedInfoFromAPI()
{
return API.Download("/stats/season/advanced?year=2010");
}
public static async Task<Teams> _addTeamToDB(Teams item)
{
var tmp = new Teams
{
School = item.School,
Abbreviation = item.Abbreviation,
Conference = item.Conference,
Divison = item.Divison,
Color = item.Color,
Alt_Color = item.Alt_Color,
Team = await _getAdvancedInfoFromAPI(item.Conference)
};
return tmp;
}
public static async Task<string> _getAdvancedInfoFromAPI(string _conferenceName)
{
List<Advanced> advancedDataList = new List<Advanced>();
var advancedData = await _downloadAdvancedInfoFromAPI();
var advancedDataDeserialized = JsonSerializer.Deserialize<Advanced[]>(advancedData, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
foreach (var item in advancedDataDeserialized)
{
advancedDataList.Add(new Advanced
{
Team = item.Team,
//Conference = item.Conference,
Year = item.Year,
excludeGarbageTime = item.excludeGarbageTime,
startWeek = item.startWeek,
endWeek = item.endWeek
});
}
return await _lookForMatch(_conferenceName, advancedDataList);
}
public static async Task<string> _lookForMatch(string _Confa, List<Advanced> lista)
{
return lista
.Where(x => x.Conference == _Confa)
.Select(x => x.Team)
.FirstOrDefault();
}
public static async Task Main()
{
Console.WriteLine("Odpaliłem program!\n");
using var db = new Context();
db.Database.EnsureCreated();
Console.WriteLine("Stworzylem baze!\n");
var teams = await _downloadTeamsFromAPI();
var deserializer = JsonSerializer.Deserialize<Teams[]>(teams, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
Console.WriteLine("Zdeserializowalem dane!\n");
foreach (var item in deserializer)
{
db.Teams.Add(await _addTeamToDB(item));
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
Console.WriteLine("Dodalem element do bazy...\n");
};
db.SaveChanges();
Console.WriteLine("Zapisalem dane do bazy!");
}
}
I know it's a noob question but I don't know how to make it work :/
I want to make it a bit asynchronous, because the words async and await doesn't exactly make it more asynchronous, but I don't know how to make it work anyhow asynchronous.
The app first downloads the information from API, deserializes it and stores it into var type variable. Then it downloads the advanced info from API and joins it by "Conference" item. (that is on purpose, even though it's not optimal).
There are a lot of asyncs and awaits but I don't think it anyhow runs asynchronously. What should I change to make this app actually async?
I appreciate your motive to write asynchronous code to make your application more scalable.
However after going through the sample code posted, I am afraid you need to do more learning on the concepts of asynchronous programming rather than just jumping into the console and trying to write some code which looks like asynchronous code.
Start slowly and try to understand the purpose of Task library, when to use it. What await does behind the scenes. When to wrap a return type with Task and when to mark a method as async. These are some of the main keywords which you come across in asynchronous code and a good understanding of these is a must to write/understand asynchronous code.
There are plenty of resources available online to get a hang of these concepts. For starters, you can begin looking into Microsoft Documentation
Having said that, inline is a rewrite of the sample code with proper use of async/await.
Please use this for references purpose only. Do not try to put into some production code until unless you have a good understanding of the concept.
Necessary comments are provided to explain some critical changes made.
class Website
{
public Website(string link)
{
_linkToWeb = new RestClient(link);
}
public async Task<string> DownloadAsync(string path)
{
var request = new RestRequest(path, Method.GET);
var response = await _linkToWeb.ExecuteAsync(request); //await an asynchronous call.
return response.Content; //No need to call response.Result. response content can be fetched after successful completion of asynchronous call.
}
public RestClient _linkToWeb { get; set; }
}
class Program
{
public static Website API = new Website("https://api.collegefootballdata.com");
public static async Task<string> _downloadTeamsFromAPI()
{
return await API.DownloadAsync("/teams/fbs");
}
public static async Task<string> _downloadAdvancedInfoFromAPI()
{
return await API.DownloadAsync("/stats/season/advanced?year=2010");
}
public static async Task<Teams> _addTeamToDB(Teams item)
{
var tmp = new Teams
{
School = item.School,
Abbreviation = item.Abbreviation,
Conference = item.Conference,
Divison = item.Divison,
Color = item.Color,
Alt_Color = item.Alt_Color,
Team = await _getAdvancedInfoFromAPI(item.Conference)
};
return tmp;
}
//Return type has to be Task<Teams> rather than Task<string> because the return object is Teams.
public static async Task<Teams> _getAdvancedInfoFromAPI(string _conferenceName)
{
List<Advanced> advancedDataList = new List<Advanced>();
var advancedData = await _downloadAdvancedInfoFromAPI();
var advancedDataDeserialized = JsonSerializer.Deserialize<Advanced[]>(advancedData, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
foreach (var item in advancedDataDeserialized)
{
advancedDataList.Add(new Advanced
{
Team = item.Team,
//Conference = item.Conference,
Year = item.Year,
excludeGarbageTime = item.excludeGarbageTime,
startWeek = item.startWeek,
endWeek = item.endWeek
});
}
return _lookForMatch(_conferenceName, advancedDataList);
}
//Return type is Teams and not string.
//Moreover Task<string> not required because we are not awaiting method call in this method.
public static Teams _lookForMatch(string _Confa, List<Advanced> lista)
{
return lista.Where(x => x.Conference == _Confa)
.Select(x => x.Team)
.FirstOrDefault();
}
public static async Task Main()
{
Console.WriteLine("Odpaliłem program!\n");
using var db = new Context();
db.Database.EnsureCreated();
Console.WriteLine("Stworzylem baze!\n");
var teams = await _downloadTeamsFromAPI();
var deserializer = JsonSerializer.Deserialize<Teams[]>(teams, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
Console.WriteLine("Zdeserializowalem dane!\n");
foreach (var item in deserializer)
{
db.Teams.Add(await _addTeamToDB(item));
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
Console.WriteLine("Dodalem element do bazy...\n");
};
db.SaveChanges();
Console.WriteLine("Zapisalem dane do bazy!");
}
}
How would I go about modifying existing code to call other controllers stored dictionary information? (without recalling the db multiple times, but just once at the start of the rest api's life).
Atm (I think) am storing the information in a dictionary (PipeMaterials) correctly. Now I'm lost on how to go about getting the information out to other controller.
Controller storing information
Controller wanting to consume information
Storing
public class MaterialsController : ControllerBase
{
public Dictionary<int, Materials> PipeMaterials;
public Dictionary<int, Rank> Ranks;
public Dictionary<int, Sumps> Sumps;
private readonly UMMClient23Context _context;
public MaterialsController(UMMClient23Context context)
{
_context = context;
LoadMaterials();
}
public void LoadMaterials()
{
PipeMaterials = new Dictionary<int, Materials>();
Task<MaterialsObjects> task = GetMaterials();
var result = task.Result;
foreach (var item in result.Ummmaterials)
{
if (!PipeMaterials.TryAdd(item.MaterialsId, item))
{
Console.Error.WriteLine("Could not load material: " + item.MaterialsName);
}
}
}
// GET: api/Materials
[HttpGet]
public async Task<MaterialsObjects> GetMaterials()
{
MaterialsObjects returnable = new MaterialsObjects();
returnable.Ummmaterials = await _context.Materials.ToListAsync();
return returnable;
}
// GET: api/MaterialDescription/5
[HttpGet("{materialsId}")]
public string GetMaterialDescription(int materialsId)
{
Materials item;
if (PipeMaterials.TryGetValue(materialsId, out item))
{
return item.MaterialsName;
}
else
{
return null;
}
//var materials = _context.Materials
// .Where(m=> m.MaterialsId == materialsId)
// .Select(m => m.MaterialsName)
// .FirstOrDefault();
}
Consuming
public class PipeController : ControllerBase
{
MaterialsController materialsController;
UMMDBHelper uMMDBHelper;
private readonly UMMClient23Context _context;
public PipeController(UMMClient23Context context)
{
_context = context;
uMMDBHelper = new UMMDBHelper(context);
}
//GET: api/Pipe
[HttpGet]
public async Task<ActionResult<IEnumerable<Data>>> Get(string value)
{
return await _context.Data.ToListAsync();
}
// GET: api/Pipe/{assestNumber}
[HttpGet("{assetNumber}")] // Make return into Object
public PipeListObject GetPipe(string assetNumber)
{
PipeListObject returnable = new PipeListObject();
Pipe Pipe = uMMDBHelper.GetPipe(assetNumber);
returnable.UmmPipes.Add(Pipe);
return returnable;
}
//GET: api/PipeMaterial/{materialId}
[HttpGet("{materialId}")]
public string GetPipeMaterial(int materialId)
{
var desc = materialsController.GetMaterialDescription(materialId);
return desc;
}
You could create a new MaterialsController directly in PipeController like
var desc = new MaterialsController(_context).GetMaterialDescription(materialId);
Or you could use HttpClient like
[HttpGet("{materialId}")]
public string GetPipeMaterial(int materialId)
{
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://localhost:44317/api/MaterialDescription/5");
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
return result;
}
}
return "";
}
Context:
I consume a ERP WebService exposing N methods like:
FunctionNameResponse FunctionName(FunctionNameQuery query)
I made a functional wrapper in order to:
Get rid off wrapper object FunctionNameResponse and FunctionNameQuery, that every method has.
One instance of the WebService for all the program.
Investigate and log error in the wrapper.
Investigate Slow running and Soap envelope with IClientMessageInspector
Duplicated code:
For each of the methods of the WebService I end up with around thirty lines of code with only 3 distinct words. Type response, type query, method name.
public FooResponse Foo(FooQuery query)
{
// CheckWebServiceState();
FooResponse result = null;
try
{
result =
WSClient
.Foo(query)
.response;
}
catch (Exception e)
{
// SimpleTrace();
// SoapEnvelopeInterceptorTrace();
// TimeWatch_PerformanceIEndpointBehaviorTrace();
}
return result;
}
I would like to reduce those repetition. In order to :
Make it easier to add a Method;
Avoid copy pasting programming with no need to understand what you are doing.
Easier to add specific catch and new test without the need to copy past in every method.
The following code work and exist only in the imaginary realm. It's a not functional sketch of my solution using my limited understanding.
public class Demo
{
public enum WS_Method
{
Foo,Bar,FooBar
}
public class temp
{
public Type Query { get; set; }
public Type Response { get; set; }
public WS_Method MethodName { get; set; }
}
public static IEnumerable<temp> TestFunctions =>
new List<temp>
{
new temp{Query=typeof(FooQuery), Response=typeof(FooResponse), MethodName=WS_Method.Foo },
new temp{Query=typeof(BarQuery), Response=typeof(BarResponse), MethodName=WS_Method.Bar },
new temp{Query=typeof(FooBarQuery), Response=typeof(FooBarResponse), MethodName=WS_Method.FooBar },
};
public static void Run()
{ // Exemple of consuming the method
var input = new BarQuery { Bar_Label = "user input", Bar_Ig = 42 };
BarResponse result = Execute<BarQuery, BarResponse>(input);
}
public static T2 Execute<T1,T2>(T1 param) {
//Get temp line where Query type match Param Type.
var temp = TestFunctions.Single(x => x.Query == typeof(T1));
var method = typeof(DemoWrapper).GetMethod(temp.MethodName.ToString(), new Type[] { typeof(T1) });
var wsClient = new DemoWrapper();
T2 result = default(T2);
try
{
result =
method
.Invoke(wsClient, new object[] { param })
.response;
}
catch (Exception e)
{
// SimpleTrace();
// SoapEnvelopeInterceptorTrace();
// TimeWatch_PerformanceIEndpointBehaviorTrace();
}
return result;
}
}
I know the reflection is heavy and perhaps it's not the right way to achieve this refactoring. So the question is:
How do I refactor those function?
attachment : Live demo https://dotnetfiddle.net/aUfqNp.
In this scenario:
You have a larger block of code which is mostly repeated
The only difference is a smaller unit of code that's called inside the larger block
You can refactor this by passing the smaller unit of code as a Func or Action as a parameter to the larger function.
In that case your larger function looks like this:
public TResponse GetResponse<TResponse>(Func<TResponse> responseFunction)
{
var result = default(TResponse);
try
{
result = responseFunction();
}
catch (Exception e)
{
// SimpleTrace();
// SoapEnvelopeInterceptorTrace();
// TimeWatch_PerformanceIEndpointBehaviorTrace();
}
return result;
}
The individual functions which call it look like this, without all the repeated code:
public FooResponse Foo(FooQuery query)
{
return GetResponse(() => WSClient.Foo(query));
}
Here's another approach where you keep the methods but have them all call a method that handles the duplication.
public class Demo
{
private _wsClient = new DemoWrapper();
public static void Run()
{ // Exemple of consuming the method
var input = new BarQuery { Bar_Label = "user input", Bar_Ig = 42 };
BarResponse result = Bar(input);
}
public FooResponse Foo(FooQuery foo) =>
Execute(foo, query => _wsClient.Foo(query));
public BarResponse Bar(BarQuery bar) =>
Execute(bar, query => _wsClient.Bar(query));
public FooBarResponse FooBar(FooBarQuery fooBar) =>
Execute(fooBar, query => _wsClient.FooBar(query));
private static TResponse Execute<TQuery ,TResponse>(
TQuery param, Func<TQuery, TResponse> getResponse)
{
//Get temp line where Query type match Param Type.
var result = default(TResponse);
try
{
result = getResponse(query);
}
catch (Exception e)
{
// SimpleTrace();
// SoapEnvelopeInterceptorTrace();
// TimeWatch_PerformanceIEndpointBehaviorTrace();
}
return result;
}
}
This contrived example is roughly how my code is structured:
public abstract class SuperHeroBase
{
protected SuperHeroBase() { }
public async Task<CrimeFightingResult> FightCrimeAsync()
{
var result = new CrimeFightingResult();
result.State = CrimeFightingStates.Fighting;
try
{
await FightCrimeOverride(results);
}
catch
{
SetError(results);
}
if (result.State == CrimeFightingStates.Fighting)
result.State = CrimeFightingStates.GoodGuyWon;
return result;
}
protected SetError(CrimeFightingResult results)
{
result.State = CrimeFightingStates.BadGuyWon;
}
protected abstract Task FightCrimeOverride(CrimeFightingResult results);
}
public enum CrimeFightingStates
{
NotStarted,
Fighting,
GoodGuyWon, // success state
BadGuyWon // error state
}
public class CrimeFightingResult
{
internal class CrimeFightingResult() { }
public CrimeFightingStates State { get; internal set; }
}
Now I'm trying to build a collection that would hold multiple SuperHero objects and offer a AllHerosFightCrime method. The hero's should not all fight at once (the next one starts when the first is finished).
public class SuperHeroCollection : ObservableCollection<SuperHeroBase>
{
public SuperHeroCollection() { }
// I mark the method async...
public async IObservable<CrimeFightingResult> AllHerosFightCrime()
{
var heros = new List<SuperHeroBase>(this);
var results = new ReplaySubject<CrimeFightingResult>();
foreach (var hero in heros)
{
// ... so I can await on FightCrimeAsync and push
// the result to the subject when done
var result = await hero.FightCrimeAsync();
results.OnNext(result);
}
results.OnCompleted();
// I can't return the IObservable here because the method is marked Async.
// It expects a return type of CrimeFightingResult
return results;
}
}
How can I return the IObservable<CrimeFightingResults> and still have the call to FightCrimeAsync awaited?
You could turn your task into an observable and combine them using Merge:
public IObservable<CrimeFightingResult> AllHerosFightCrime()
{
var heros = new List<SuperHeroBase>(this);
return heros.Select(h => h.FightCrimeAsync().ToObservable())
.Merge();
}
If you want to maintain the order your events are received you can use Concat instead of Merge.