I am currently having a problem when it comes to static fields in C#, the problem is in the way EasyPost instantiates their ClientManger however, I think someone with a better understanding regarding static fields might be able to help me.
I am creating a plugin that allows for multiple users to access EasyPost for tracking off parcels.
I have written a Unit test to test the scenario where multiple people use it at the same time.
Unit Test:
[TestMethod()]
public void TestMultiInit()
{
var Client2 = new cpEasyPost("123");
var Client = new cpEasyPost("123456qq785412");
var Shipment = Client.CreateShipment(
new EasyPost.Address
{
street1 = "417 MONTGOMERY ST",
street2 = "FLOOR 5",
city = "SAN FRANCISCO",
state = "CA",
zip = "94104",
country = "US",
company = "EasyPost"
},
new EasyPost.Address
{
street1 = "417 MONTGOMERY ST",
street2 = "FLOOR 5",
city = "SAN FRANCISCO",
state = "CA",
zip = "94104",
country = "US",
company = "EasyPost"
},
new EasyPost.Parcel
{
length = 20.2,
width = 10.9,
height = 5,
weight = 65.9
});
var Shipment2 = Client2.CreateShipment(
new EasyPost.Address
{
street1 = "417 MONTGOMERY ST",
street2 = "FLOOR 5",
city = "SAN FRANCISCO",
state = "CA",
zip = "94104",
country = "US",
company = "EasyPost"
},
new EasyPost.Address
{
street1 = "417 MONTGOMERY ST",
street2 = "FLOOR 5",
city = "SAN FRANCISCO",
state = "CA",
zip = "94104",
country = "US",
company = "EasyPost"
},
new EasyPost.Parcel
{
length = 20.2,
width = 10.9,
height = 5,
weight = 65.9
});
}
The issue is Client2 has incorrect key so if I try and create a shipment with it should fail however because of ClinetManager being static is uses Client init of it because if was later.
Here is my ctor:
public cpEasyPost(string secretKey)
{
SecretKey = secretKey;
//Original way of init Client
//ClientManager.SetCurrent(SecretKey);
//Create ClientManager
ClientManager.SetCurrent(() => new Client(new ClientConfiguration(SecretKey)));
}
And here is my method:
public Shipment CreateShipment(Address AddressFrom, Address AddressTo, Parcel Parcel, CustomsInfo customs = null, string CustomReference = "", double? InsauranceAmount = null)
{
//Validate Adress
var isValidFrom = ValidateAddress(AddressFrom);
var isValidTo = ValidateAddress(AddressFrom);
if (!isValidFrom.isSuccess)
throw new Exception("Address From is not Valid");
if (!isValidTo.isSuccess)
throw new Exception("Address To is not Valid");
//Validate Pacrcel
var isValidParcel = ValidateParcel(Parcel);
if (!isValidFrom.isSuccess)
throw new Exception("Parcel is not Valid");
//Create Shipment
Shipment shipment = new Shipment()
{
reference = CustomReference,
to_address = AddressTo,
from_address = AddressFrom,
parcel = Parcel,
customs_info = customs
};
//ClientManager.SetCurrent(SecretKey); **
shipment.Create();
//Add Insurance
if (InsauranceAmount != null)
shipment.Insure(InsauranceAmount.Value);
return shipment;
}
No the issue I have is that the ClinetManager is static, this is a locked DDL so I can't modify this. In the method, I considered ssetting the manager before every call however this does not seem like the best solution as it could still theoretically lead to issues I marked this with **.
Any help would be greatly appreciated. Thank you in adnvacne.
At the end, I just rebuild the code from their SDK into something that fitted my needs better. There is no other way around this.
Related
I try add payment method to my project where customer can buy product from other user. But i have problem because when i use it:
LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
Price = "{{PRICE_ID}}",
Quantity = 1,
},
},
Mode = "payment",
SuccessUrl = "https://example.com/success",
CancelUrl = "https://example.com/cancel",
PaymentIntentData = new SessionPaymentIntentDataOptions
{
ApplicationFeeAmount = 123,
},
};
var requestOptions = new RequestOptions
{
StripeAccount = "{{CONNECTED_ACCOUNT_ID}}",
};
var service = new SessionService();
Session session = service.Create(options, requestOptions);
PRICE_ID can't be price's on my main stripe account and i must create product and price on the connected account.
In the case of creating a product on the main account, I do it like this:
var options = new ProductCreateOptions
{
Id = ProductId.ToString(),
Name = "Product_name",
DefaultPriceData = new ProductDefaultPriceDataOptions
{
UnitAmount = price,
Currency = "pln"
},
Expand = new List<string> { "default_price" },
};
_productService.Create(options);
How create product and price on the connected account with my api?
The Stripe API has a Stripe-Account header where you can pass in the ID of one of your connected accounts to make the call as that account. In C# that would look like this:
{
Id = ProductId.ToString(),
Name = "Product_name",
DefaultPriceData = new ProductDefaultPriceDataOptions
{
UnitAmount = price,
Currency = "pln"
},
Expand = new List<string> { "default_price" },
StripeAccount = "acct_123456789",
};
_productService.Create(options);
I am using stripe for payments.
When I create the SessionCreateOptions object I add the CustomerId and ProductId for later usage in my Webhook.
var options = new SessionCreateOptions
{
PaymentMethodTypes = new List<string> {
"card",
},
CustomerEmail = buyer.Email,
LineItems = new List<SessionLineItemOptions> {
new SessionLineItemOptions {
Name = packages.First().Name,
Description = packages.First().Description,
Amount = (long)(totalAmount * 100),
Currency = "eur",
Quantity = 1,
},
},
SuccessUrl = appSettings.RedirectHost.Url + "/Checkouts/Show/success?session_id={CHECKOUT_SESSION_ID}",
CancelUrl = appSettings.RedirectHost.Url + "/Checkouts/Show/failed",
Metadata = new Dictionary<String, String>()
{
{ "CustomerId", buyer.Id.ToString()},
{ "ProductId", packages.First().Id.ToString()}
},
};
After a successful payment the webhook gets called and retrieves to object with customer data, price and other values, but the metadata dictionary is empty.
You are retrieving the PaymentIntent that was created by the CheckoutSession, but you're setting the metadata on the CheckoutSession itself.
There are two options, depending on where you want to store and retrieve the metadata. You can retrieve the CheckoutSession directly [0], or you change your code to set the metadata on the PaymentIntent when creating the CheckoutSession, via payment_intent_data.metadata [1].
[0] https://stripe.com/docs/api/checkout/sessions/retrieve
[1] https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-payment_intent_data-metadata
Here's my code :
public partial class ActivityService
{
public SearchActivityOutput GetActivityFromDbByName(SearchActivityInput input)
{
using (var conn = DbService.GetInstance().GetOpenConnection())
{
var savedActivities = GetSearchResultByNameQuery.GetInstance()
.Execute(conn, new { Name = input.Name });
var activityList = savedActivities.Select(a => new ActivityDetail()
{
Name = a.Name,
City = a.City,
Country = a.Country,
Description = a.Description,
OperationTime = a.OperationTime,
Price = a.Price
}).ToList();
var output = new SearchActivityOutput
{
ActivityList = activityList
};
return output;
}
}
}
How can i create unit test from that class?
my sample unit test for that class:
[TestMethod()]
public void GetActivityFromDbByNameTest()
{
Initializer.Init();
var input = new SearchActivityInput { Name = "Marjan" };
var ActList1 = new ActivityDetail()
{ Name = "Marjan", City = "Bandung", Country = "Indonesia", Description = "coba", OperationTime = "24 Jam", Price = 2000};
var ActList2 = new ActivityDetail()
{ Name = "Marjan", City = "Bandung", Country = "Indonesia", Description = "coba", OperationTime = "24 Jam", Price = 3500 };
var ActList3 = new ActivityDetail()
{ Name = "Marjan aja", City = "Jakarta", Country = "Indonesia", Description = "apapun", OperationTime = "2 Hari", Price = 4500 };
var ActList4 = new ActivityDetail()
{ Name = "Marjan", City = "Stockholm", Country = "Swedia", Description = "123coba", OperationTime = "3 Jam", Price = 3500 };
var ActList5 = new ActivityDetail()
{ Name = "Marjan", City = "Stockholm", Country = "Swedia", Description = "123coba", OperationTime = "3 Jam", Price = 4500 };
var ActList6 = new ActivityDetail()
{ Name = "Marjan aja", City = "Jakarta", Country = "Indonesia", Description = "apapun", OperationTime = "2 Hari", Price = 2000 };
var ActList7 = new ActivityDetail()
{ Name = "Marjan", City = "Stockholm", Country = "Swedia", Description = "123coba", OperationTime = "3 Jam", Price = 3500 };
var ActList8 = new ActivityDetail()
{ Name = "Marjan", City = "Stockholm", Country = "Swedia", Description = "123coba", OperationTime = "3 Jam", Price = 2000 };
var ActList9 = new ActivityDetail()
{ Name = "Marjan", City = "Stockholm", Country = "Swedia", Description = "123coba", OperationTime = "3 Jam", Price = 2000 };
var ActList = new List<ActivityDetail>();
ActList.Add(ActList1);
ActList.Add(ActList2);
ActList.Add(ActList3);
ActList.Add(ActList4);
ActList.Add(ActList5);
ActList.Add(ActList6);
ActList.Add(ActList7);
ActList.Add(ActList8);
ActList.Add(ActList9);
var expectedResult = new SearchActivityOutput
{
ActivityList = ActList
};
using (var conn = DbService.GetInstance().GetOpenConnection())
{
var actualResult = ActivityService.GetInstance().GetActivityFromDbByName(input);
Assert.AreEqual(expectedResult, actualResult);
}
}
but, when i run the unit test, there give some error message :
Test Name: GetActivityFromDbByNameTest Test
Result StackTrace: at
xxxx.ActivityServiceTests.GetActivityFromDbByNameTest()
in
70 Result Message: Assert.AreEqual failed.
Expected:(xxx.yyy.zzz.Model.SearchActivityOutput).
Actual:(xxx.yyy.zzz.Model.SearchActivityOutput).
You should not use the database while your unittesting.
Wikipedia says: A common example of this is classes that depend on a database: in order to test the class, the tester often writes code that interacts with the database. This is a mistake, because a unit test should usually not go outside of its own class boundary, and especially should not cross such process/network boundaries because this can introduce unacceptable performance problems to the unit test-suite.
Use testdata or a dummy class that represent your Database.
https://softwareengineering.stackexchange.com/questions/119367/should-service-test-classes-connect-to-the-database
https://softwareengineering.stackexchange.com/questions/138238/unit-testing-database-coupled-app/138257#138257
You cannot unit test a class that performs any I/O-related tasks. Even if your tests seem to run fine on your development machine, they will likely fail on your colleague's machine or CI server.
In order for your piece of code to be testable, it should be a either a pure function, or it should be reducible to a pure function using some abstraction techniques like IoC / higher order functions / etc.
Learn to write testable code first. This article will give you some advise - https://www.toptal.com/resume/sergey-kolodiy (I'm the author of it).
I completely agree with the other statements about not using the database in your unit tests, but the answer to the failure that you're currently receiving is that you should be using methods of the CollectionAssert class to compare the contents of two collections instead of the Assert class that you're using. If you just want to ensure that the returned List contains the same elements regardless of ordering, you can use
CollectionAssert.AreEquivalent(expectedResult, actualResult);
If sequencing order is important, you should use
CollectionAssert.AreEqual(expectedResult, actualResult);
Every time I run the application same objects are added to the database(duplicates).
My Global.asax:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using WebApplication2.Migrations;
using WebApplication2.Models;
namespace WebApplication2 {
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());
//Database.SetInitializer(new DropCreateDatabaseAlways<ApplicationDbContext>());
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
and My Configuration.cs with Seed method:
namespace WebApplication2.Migrations
{
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using WebApplication2.Models;
internal sealed class Configuration : DbMigrationsConfiguration<WebApplication2.Models.ApplicationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
ContextKey = "WebApplication2.Models.ApplicationDbContext";
}
protected override void Seed(WebApplication2.Models.ApplicationDbContext context) {
var persons = new List<Person> {
new Person{FirstName = "John", LastName = "Doe", CellNumber = "123-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "312312312", Notes = "Annoying"},
new Person{FirstName = "Anna", LastName = "Doe", CellNumber = "113-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "548555672", Notes = "Less Annoying"}
};
persons.ForEach(person => context.Persons.AddOrUpdate(person));
context.SaveChanges();
var meetings = new List<Meeting>{
new Meeting{PersonId = 1, Body = "Body of meeting", Date = DateTime.Now}
};
meetings.ForEach(meeting => context.Meetings.AddOrUpdate(meeting));
context.SaveChanges();
var statuses = new List<Status> {
new Status{Name = "OK"},
new Status {Name = "NOT_OK"}
};
statuses.ForEach(status => context.Statuses.AddOrUpdate(status));
context.SaveChanges();
}
}
}
Every time I run the app Seed adds duplicate records:
I needed to comment contents of Seed method to prevent adding duplicates.
Question: (1) What should I change so Seed method will be run only to recreate database after migration?
EDIT:
In the Seed method there is comment:
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
but my method is called ALWAYS, not only after migrations. Why is it so?
From this page (about halfway down), which was sourced from this answer
Note: Adding code to the Seed method is one of many ways that you can insert fixed data into the database. An alternative is to add code to the Up and Down methods of each migration class. The Up and Down methods contain code that implements database changes. You'll see examples of them in the Deploying a Database Update tutorial.
You can also write code that executes SQL statements by using the Sql method. For example, if you were adding a Budget column to the Department table and wanted to initialize all department budgets to $1,000.00 as part of a migration, you could add the folllowing line of code to the Up method for that migration:
Sql("UPDATE Department SET Budget = 1000");
You might also look into using the AddOrUpdate method, as referenced in this answer, which should also work for your purposes.
I quickly changed the code I obtained from the answer linked above, so bear with me if there's an issue with the code below. The concept should still be relatively clear, I believe.
context.People.AddOrUpdate(c => c.PK, new Person() { PK = 0, FirstName = "John", ... })
context.People.AddOrUpdate(c => c.PK, new Person() { PK = 1, FirstName = "Anna", ... })
You have full access to the context in the Seed method, so you can query to see if data already exists.
For example, you can seed the tables only if they're empty...
protected override void Seed(WebApplication2.Models.ApplicationDbContext context) {
if (!context.Persons.Any())
{
var persons = new List<Person> {
new Person{FirstName = "John", LastName = "Doe", CellNumber = "123-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "312312312", Notes = "Annoying"},
new Person{FirstName = "Anna", LastName = "Doe", CellNumber = "113-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "548555672", Notes = "Less Annoying"}
};
persons.ForEach(person => context.Persons.Add(person));
context.SaveChanges();
}
if (!context.Meetings.Any())
{
var meetings = new List<Meeting>{
new Meeting{PersonId = 1, Body = "Body of meeting", Date = DateTime.Now}
};
meetings.ForEach(meeting => context.Meetings.Add(meeting));
context.SaveChanges();
}
if (!context.Statuses.Any())
{
var statuses = new List<Status> {
new Status{Name = "OK"},
new Status {Name = "NOT_OK"}
};
statuses.ForEach(status => context.Statuses.Add(status));
context.SaveChanges();
}
}
You can also use AddOrUpdate, but you need to tell EF how to check if the record exists using the first parameter...
protected override void Seed(WebApplication2.Models.ApplicationDbContext context) {
var persons = new List<Person> {
new Person{FirstName = "John", LastName = "Doe", CellNumber = "123-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "312312312", Notes = "Annoying"},
new Person{FirstName = "Anna", LastName = "Doe", CellNumber = "113-456-789", SecondaryPhoneNumber = "98873213", Address = "1street 2",BirthDate = DateTime.Now.Date, Pesel = "548555672", Notes = "Less Annoying"}
};
persons.ForEach(person => context.Persons.AddOrUpdate(p => new { p.FirstName, p.LastName }, person));
context.SaveChanges();
var meetings = new List<Meeting>{
new Meeting{PersonId = 1, Body = "Body of meeting", Date = DateTime.Now}
};
meetings.ForEach(meeting => context.Meetings.AddOrUpdate(m => m.Body, meeting));
context.SaveChanges();
var statuses = new List<Status> {
new Status{Name = "OK"},
new Status {Name = "NOT_OK"}
};
statuses.ForEach(status => context.Statuses.AddOrUpdate(s => s.Name, status));
context.SaveChanges();
}
Question: (1) What should I change so Seed method will be run only to
recreate database after migration?
If you only need to seed data when your database is created. In this case, you can create a Database Initialiser from CreateDatabaseIfNotExist Initialiser. Then in the DatabaseInitialiser class, you can override the Seed Method with your data there, instead of the MigrationConfiguration class. Further information can be found in attached link.
Database Initialization Strategies in Code-First:
but my method is called ALWAYS, not only after migrations. Why is it
so?
In migration configuration. the seed method will be called every time the database migration happens. That is why your seed method is called all the time.
var paidOutType = new List<PaidOutType>
{
new PaidOutType { PaidOutTypeID = 1, Code = "001", Description = "PAID OUT 1", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
new PaidOutType { PaidOutTypeID = 2, Code = "002", Description = "PAID OUT 2", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
new PaidOutType { PaidOutTypeID = 3, Code = "002", Description = "PAID OUT 3", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
};
paidOutType.ForEach(u => smartPOSContext.PaidOutType.AddOrUpdate(u));
smartPOSContext.SaveChanges();
This worked for me
Delete all the rows in the table
Reset the incremental identity to 0 if it is activated (the primary keys specified in the seed() must match those in the database table so that they do not duplicate)
Specify the primary keys in the 'seed' method
Run the seed () method several times and you check if they duplicated
First, reset your primary key to be sure that there would be no duplicate keys
// reset identity autoincrement to 0
context.Database.ExecuteSqlCommand("DBCC CHECKIDENT('tableName', RESEED, 0)");
Then use AddOrUpdate method to seed data
context.People.AddOrUpdate(new Person
{
Id = 1,
Name = "John Doe"
});
I know how to insert data into a database one by one but I want to insert multiple pieces of data into database and I don't know how to do this.
Here is my code for a "one-time" insert of data:
Bloggers BlogerToaddData = new Blogger
{
Interest = txtIntrest.Text.ToString(),
Name = txtname.Text.ToString(),
Totalposts = Convert.ToInt32(txtPost.Text)
};
bloggerDB.bloggers.InsertOnSubmit(BlogerToaddData);
bloggerDB.SubmitChanges();
but how to insert multiple rows of data?
Bloggers BlogerToaddData = new Bloggers
{Interest = "wy",Name = "opwm",Totalposts =1},
{Interest = "wy",Name = "opwm",Totalposts =1},
{Interest = "wy",Name = "opwm",Totalposts =1},
This is not working
Well you need to create multiple objects. You can either add them one at a time to the list of pending changes:
var bloggs = bloggerDB.bloggers;
bloggers.InsertOnSubmit(new Bloggers { Interest = "wy", Name = "opwn", TotalPosts = 1 });
bloggers.InsertOnSubmit(new Bloggers { Interest = "ab", Name = "abcd", TotalPosts = 2 });
bloggers.InsertOnSubmit(new Bloggers { Interest = "cd", Name = "1234", TotalPosts = 3 });
bloggerDB.SubmitChanges();
Or use InsertAllOnSubmit:
bloggerDB.bloggers.InsertAllOnSubmit(new[] {
new Bloggers { Interest = "wy", Name = "opwn", TotalPosts = 1 },
new Bloggers { Interest = "ab", Name = "abcd", TotalPosts = 2 },
new Bloggers { Interest = "cd", Name = "1234", TotalPosts = 3 }
});
bloggerDB.SubmitChanges();