C# / VBS Automate Team Project Creation in TFS 2015 Update 3 - c#

I'm looking for a way to automate team project creation in TFS 2015 Update 3.
I did a quick crawl over the web and found various posts on how to do it but nothing specific to 2015 version update 3.
Some links I found:
#1
#2
I'd like to do it as simple and lightweight as possible.
A rough idea would be to fill up all the details needed e.g.:
Sign in details, server, collection, project name, etc... on an excel, save the information somewhere (like an xml for presented on link#2) and trigger a batch file to do the necessary stuff via vbs macro.
To be honest I do not know where to start yet, like how to even automate the project creation part.
Appreciate if you can point me in the right path to start this out. Ideas are also welcome :). Thanks in advance!

You could use this REST API to create a team project. TFS also provide to using C# code to create a team project:
public static TeamProject CreateProject()
{
string projectName = "Sample project";
string projectDescription = "Short description for my new project";
string processName = "Agile";
VssCredentials c = new VssCredentials(new Microsoft.VisualStudio.Services.Common.WindowsCredential(new NetworkCredential("username", "password", "domain")));
VssConnection connection = new VssConnection(new Uri("http://v-tinmo-12r2:8080/tfs/MyCollection"),c);
// Setup version control properties
Dictionary<string, string> versionControlProperties = new Dictionary<string, string>();
versionControlProperties[TeamProjectCapabilitiesConstants.VersionControlCapabilityAttributeName] =
SourceControlTypes.Git.ToString();
// Setup process properties
ProcessHttpClient processClient = connection.GetClient<ProcessHttpClient>();
Guid processId = processClient.GetProcessesAsync().Result.Find(process => { return process.Name.Equals(processName, StringComparison.InvariantCultureIgnoreCase); }).Id;
Dictionary<string, string> processProperaties = new Dictionary<string, string>();
processProperaties[TeamProjectCapabilitiesConstants.ProcessTemplateCapabilityTemplateTypeIdAttributeName] =
processId.ToString();
// Construct capabilities dictionary
Dictionary<string, Dictionary<string, string>> capabilities = new Dictionary<string, Dictionary<string, string>>();
capabilities[TeamProjectCapabilitiesConstants.VersionControlCapabilityName] =
versionControlProperties;
capabilities[TeamProjectCapabilitiesConstants.ProcessTemplateCapabilityName] =
processProperaties;
//Construct object containing properties needed for creating the project
TeamProject projectCreateParameters = new TeamProject()
{
Name = projectName,
Description = projectDescription,
Capabilities = capabilities
};
// Get a client
ProjectHttpClient projectClient = connection.GetClient<ProjectHttpClient>();
TeamProject project = null;
try
{
Console.WriteLine("Queuing project creation...");
// Queue the project creation operation
// This returns an operation object that can be used to check the status of the creation
OperationReference operation = projectClient.QueueCreateProject(projectCreateParameters).Result;
// Check the operation status every 5 seconds (for up to 30 seconds)
Operation completedOperation = WaitForLongRunningOperation(connection, operation.Id, 5, 30).Result;
// Check if the operation succeeded (the project was created) or failed
if (completedOperation.Status == OperationStatus.Succeeded)
{
// Get the full details about the newly created project
project = projectClient.GetProject(
projectCreateParameters.Name,
includeCapabilities: true,
includeHistory: true).Result;
Console.WriteLine();
Console.WriteLine("Project created (ID: {0})", project.Id);
}
else
{
Console.WriteLine("Project creation operation failed: " + completedOperation.ResultMessage);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception during create project: ", ex.Message);
}
return project;
}
private static async Task<Operation> WaitForLongRunningOperation(VssConnection connection, Guid operationId, int interavalInSec = 5, int maxTimeInSeconds = 60, CancellationToken cancellationToken = default(CancellationToken))
{
OperationsHttpClient operationsClient = connection.GetClient<OperationsHttpClient>();
DateTime expiration = DateTime.Now.AddSeconds(maxTimeInSeconds);
int checkCount = 0;
while (true)
{
Console.WriteLine(" Checking status ({0})... ", (checkCount++));
Operation operation = await operationsClient.GetOperation(operationId, cancellationToken);
if (!operation.Completed)
{
Console.WriteLine(" Pausing {0} seconds", interavalInSec);
await Task.Delay(interavalInSec * 1000);
if (DateTime.Now > expiration)
{
throw new Exception(String.Format("Operation did not complete in {0} seconds.", maxTimeInSeconds));
}
}
else
{
return operation;
}
}
}

Related

Error when creating ServiceBus Queue using Azure.Messaging.ServiceBus.Administration

I am (trying) to use this code to create ServiceBus Queue:
using Azure.Messaging.ServiceBus;
using Azure.Messaging.ServiceBus.Administration;
...
class blabla
{
private string connectionString = "Endpoint=sb://XXXX.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=XXXYYY";
private string queueName = "testqueue";
...
public doit()
{
var adminClient = new ServiceBusAdministrationClient(connectionString);
bool queueExists = adminClient.QueueExistsAsync(queueName).Result;
if (!queueExists)
{
var options = new CreateQueueOptions(queueName)
{
DefaultMessageTimeToLive = TimeSpan.FromDays(2),
LockDuration = TimeSpan.FromSeconds(45),
MaxDeliveryCount = 8,
MaxSizeInMegabytes = 2048
};
options.AuthorizationRules.Add(new SharedAccessAuthorizationRule(
"allClaims",
new[] { AccessRights.Manage, AccessRights.Send, AccessRights.Listen }));
QueueProperties createdQueue = adminClient.CreateQueueAsync(options).Result;
}
}
}
but constantly getting this error:
System.AggregateException: One or more errors occurred. (SubCode=40900. Conflict. You're requesting an operation that isn't allowed in the resource's current state. To know more visit https://aka.ms/sbResourceMgrExceptions. . TrackingId:bc79fd98-73c8-4301-b6b9-05d0eae6ed6a_G17, SystemTracker:xxx.servicebus.windows.net:yyy, Timestamp:2021-05-09T00:24:57
Status: 409 (Conflict)
ErrorCode: 40900
Using old (NET) way with NamespaceManager from Microsoft.ServiceBus works with no problems.
var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
if (!namespaceManager.QueueExists(queueName))
{
namespaceManager.CreateQueue(queueName);
}
So, does anyone knows what am I doing wrong here?
*
Below is the updated working code, you need to make sure you have shared access policy with full access.
using Azure.Messaging.ServiceBus.Administration;
using System;
using System.Threading.Tasks;
namespace ServiceBusDemo
{
class Program
{
private static string connectionString = "Endpoint=sb://ns-servicebusshu.servicebus.windows.net/;SharedAccessKeyName=fullAccess;SharedAccessKey=oB+IsK8Aqp0/xfXnF9HCz6x9pqPIOysTXaJofSmHEYs=";
private static string queueName = "testqueue";
async static Task Main(string[] args)
{
await doit();
}
public static async Task doit()
{
var adminClient = new ServiceBusAdministrationClient(connectionString);
bool queueExists = await adminClient.QueueExistsAsync(queueName);
if (!queueExists)
{
var options = new CreateQueueOptions(queueName)
{
DefaultMessageTimeToLive = TimeSpan.FromDays(2),
LockDuration = TimeSpan.FromSeconds(45),
MaxDeliveryCount = 8,
MaxSizeInMegabytes = 2048
};
options.AuthorizationRules.Add(new SharedAccessAuthorizationRule("allClaims", new[] { AccessRights.Manage, AccessRights.Send, AccessRights.Listen }));
QueueProperties createdQueue = await adminClient.CreateQueueAsync(options);
}
}
}
}
Once you ran the application its successfully created the queue as below :
Maybe it's not your case... But if you have a TOPIC with the same name that you try to create your new QUEUE, QueueExistsAsync will return false, but you'll be spitted with this bizarre error at creation time. The fix is easy... changing the queue name or deleting the offending topic.
Sorry for the confusion.
My code (and Rahul Shukla as well) is working now (????).
I had to create a few new shared access policies with full access (????).
The third created started working (??).
The previous 2 I created are still not working (????).
There are no differences between the 3 policies created. Hence the question marks in my answer.
Posted question on MS NET SB forum about 1 out of 3 policies working. No answer/acknowledgment so far.

Autodesk Forge Design Automation - Inventor - failedInstructions (FailedMissingOutput)

I am trying to use the WorkItems API to extract key paramaters of a part to a text file. The work item fails with FailedMissingOutput [KeyParameters.txt] which is the file my plugin creates in the working folder. Debugging locally the file is created successfully.
Log:
Addin Code is pretty simple:
public void RunWithArguments(Document doc, NameValueMap map)
{
LogTrace("Processing " + doc.FullFileName);
LogInputData(doc, map);
try
{
var DocDir = System.IO.Path.GetDirectoryName(doc.FullFileName);
var ParametersOutputFileName = System.IO.Path.Combine(DocDir, "KeyParameters.txt");
if (doc.DocumentType == DocumentTypeEnum.kPartDocumentObject)
{
using (new HeartBeat())
{
// TODO: handle the Inventor part here
PartDocument PartDoc = (PartDocument)doc;
ExtractKeyParams(PartDoc.ComponentDefinition.Parameters, ParametersOutputFileName);
}
}
else if (doc.DocumentType == DocumentTypeEnum.kAssemblyDocumentObject) // Assembly.
{
using (new HeartBeat())
{
// TODO: handle the Inventor assembly here
AssemblyDocument AssyDoc = (AssemblyDocument)doc;
ExtractKeyParams(AssyDoc.ComponentDefinition.Parameters, ParametersOutputFileName);
}
}
}
catch (Exception e)
{
LogError("Processing failed. " + e.ToString());
}
}
public void ExtractKeyParams(Parameters Params, string OutputFileName)
{
List<string> ParamList = new List<string>();
foreach (Parameter Param in Params)
{
if (Param.IsKey)
{
ParamList.Add(Param.Name);
}
}
string[] OutputParams = ParamList.ToArray();
System.IO.File.AppendAllLines(OutputFileName, OutputParams);
}
Activity Params...
private static Dictionary<string, Parameter> GetActivityParams()
{
return new Dictionary<string, Parameter>
{
{
Constants.Parameters.InventorDoc,
new Parameter
{
Verb = Verb.Get,
Description = "File to process"
}
},
{
"OutputParams",
new Parameter
{
Verb = Verb.Put,
LocalName = "KeyParameters.txt",
Description = "Key Parameters Output",
Ondemand = false,
Required = false
}
}
};
}
.....And Work Item arguments (With token and ids removed), the signed resource is a forge bucket resource generated to expire in 60 minutes so that shouldn't be the issue,
private static Dictionary<string, IArgument> GetWorkItemArgs()
{
Dictionary<string, string> Header = new Dictionary<string, string>();
Header.Add("Authorization", "Bearer <ACCESS_TOKEN>");
Dictionary<string, string> Header2 = new Dictionary<string, string>();
Header2.Add("Authorization", "Bearer <ACCESS_TOKEN>");
Header2.Add("Content-type", "application/octet-stream");
return new Dictionary<string, IArgument>
{
{
Constants.Parameters.InventorDoc,
new XrefTreeArgument
{
Url = "https://developer.api.autodesk.com/oss/v2/buckets/<BUCKET_KEY>/objects/box.ipt",
Headers = Header
}
},
{
"OutputParams",
new XrefTreeArgument
{
Verb = Verb.Put,
Url = "https://developer.api.autodesk.com/oss/v2/signedresources/<SIGNED_RESOURCE_ID>?region=US",
Headers = Header2
}
}
};
}
I cannot work out why the KeyParameters.txt file isn't being generated by my addin, but looking at the log it seems it is and maybe the problem is uploading it to the signed resource, my token has all the needed scopes.
The KeyParameters.txt file isn't generated because your Activity calls this function Run(Document doc). It is possible to see it in your log, check this line:
InventorCoreConsole.exe Information: 0 : Run called with box.ipt
Now just try to move your code to the Run(Document doc) function.
The RunWithArguments(Document doc, NameValueMap map) function is called in case that you have any arguments in the command line in your Activity.
https://forge.autodesk.com/en/docs/design-automation/v3/developers_guide/field-guide/#command-lines
From the error message it seems like your addin is either not generating the "KeyParameters.txt" file or generating it at the wrong location.
Is it possible that your code never enter any of the if statement or it end up in the catch statement without creating the txt file?
You can download the report using the reportUrl, there might be more information in there. You might also be able to add more logging in there to help you understand what is happening.

Idempotency for BigQuery load jobs using Google.Cloud.BigQuery.V2

You are able to create a csv load job to load data from a csv file in Google Cloud Storage by using the BigQueryClient in Google.Cloud.BigQuery.V2 which has a CreateLoadJob method.
How can you guarantee idempotency with this API to ensure that say the network dropped before getting a response and you kicked off a retry you would not end up with the same data being loaded into BigQuery multiple times?
Example API usage
private void LoadCsv(string sourceUri, string tableId, string timePartitionField)
{
var tableReference = new TableReference()
{
DatasetId = _dataSetId,
ProjectId = _projectId,
TableId = tableId
};
var options = new CreateLoadJobOptions
{
WriteDisposition = WriteDisposition.WriteAppend,
CreateDisposition = CreateDisposition.CreateNever,
SkipLeadingRows = 1,
SourceFormat = FileFormat.Csv,
TimePartitioning = new TimePartitioning
{
Type = _partitionByDayType,
Field = timePartitionField
}
};
BigQueryJob loadJob = _bigQueryClient.CreateLoadJob(sourceUri: sourceUri,
destination: tableReference,
schema: null,
options: options);
loadJob.PollUntilCompletedAsync().Wait();
if (loadJob.Status.Errors == null || !loadJob.Status.Errors.Any())
{
//Log success
return;
}
//Log error
}
You can achieve idempotency by generating your own jobid based on e.g. file location you loaded and target table.
job_id = 'my_load_job_{}'.format(hashlib.md5(sourceUri+_projectId+_datasetId+tableId).hexdigest())
var options = new CreateLoadJobOptions
{
WriteDisposition = WriteDisposition.WriteAppend,
CreateDisposition = CreateDisposition.CreateNever,
SkipLeadingRows = 1,
JobId = job_id, #add this
SourceFormat = FileFormat.Csv,
TimePartitioning = new TimePartitioning
{
Type = _partitionByDayType,
Field = timePartitionField
}
};
In this case if you try reinsert the same job_id you got error.
You can also easily generate this job_id for check in case if pooling failed.
There are two places you could end up losing the response:
When creating the job to start with
When polling for completion
The first one is relatively tricky to recover from without a job ID; you could list all the jobs in the project and try to find one that looks like the one you'd otherwise create.
However, the C# client library generates a job ID so that it can retry, or you can specify your own job ID via CreateLoadJobOptions.
The second failure time is much simpler: keep the returned BigQueryJob so you can retry the polling if that fails. (You could store the job name so that you can recover even if your process dies while waiting for it to complete, for example.)

Product_Order verifyOrder API fails when adding guest_disks

I have a C# application in which I imported API methods using wsdl, as described in the Softlayer guidelines.
I'm editing virtual guests by passing a Container_Product_Order_Virtual_Guest_Upgrade structure to the Product_Order service.
Everything works great except for when adding item price IDs for guest_disks, scenario in which after about 6-7 seconds the following exception is thrown:
"The request was aborted: The connection was closed unexpectedly."
This happens with both verifyOrder and placeOrder methods.
I checked Virtual_Guest::getUpgradeItemPrices in order to make sure that the guest disk values are valid(even though passing invalid itempriceIds for the VM results in an specific error response, not in a generic exception such as the one described above).
I can't find any details in the documentation that could give me hints as why I can upgrade anything except guest_disks.
EDIT:
Stripped code as requested:
SoftLayer_Virtual_Guest[] _VMtoEditList = new SoftLayer_Virtual_Guest[1] { -- Vm instance details are retrieved from SL according to the passed VM ID; };
List<SoftLayer_Product_Item_Price> _itemPriceList = new List<SoftLayer_Product_Item_Price>();
foreach (-- collection of properties to be upgraded )
{
SoftLayer_Product_Item_Category _category = new SoftLayer_Product_Item_Category();
_category.categoryCode = -- retrieved from the collection on which I iterate (eg "guest_disk0", "ram", etc.);
SoftLayer_Product_Item_Price _itemPrice = new SoftLayer_Product_Item_Price();
_itemPrice.id = -- the item priceID for the current item;
_itemPrice.idSpecified = true;
_itemPrice.categories = new SoftLayer_Product_Item_Category[1] { _category };
_itemPriceList.Add(_itemPrice);
}
SoftLayer_Product_Item_Price[] _itemPricesArray = _itemPriceList.ToArray();
SoftLayer_Container_Product_Order_Property _property1 = new SoftLayer_Container_Product_Order_Property();
_property1.name = "NOTE_GENERAL";
_property1.value = -- order's description;
SoftLayer_Container_Product_Order_Property _property2 = new SoftLayer_Container_Product_Order_Property();
_property2.name = "MAINTENANCE_WINDOW";
_property2.value = "now";
// Build SoftLayer_Container_Product_Order_Property
SoftLayer_Container_Product_Order_Property[] properties = new SoftLayer_Container_Product_Order_Property[2] { _property1, _property2 };
-- create container
SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade _upgradeContainer = new SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade();
_upgradeContainer.virtualGuests = _VMtoEditList;
_upgradeContainer.prices = _itemPricesArray;
_upgradeContainer.properties = properties;
_upgradeContainer.packageId = 46;
_upgradeContainer.packageIdSpecified = true;
SoftLayer_Product_OrderService service = new SoftLayer_Product_OrderService();
-- authentication structure is created here
SoftLayer_Container_Product_Order _verifiedOrder = service.verifyOrder(_upgradeContainer);
service.placeOrder(_verifiedOrder, false);
Here a have an example to upgrade which works see below. I see that in your code you are adding the packageId which is not required, removed and try again.
Also when you are creating the web references try using the last version of the api in the WSDL url (v3.1)
e.g. https://api.softlayer.com/soap/v3.1/SoftLayer_Hardware_Server?wsdl
//-----------------------------------------------------------------------
// <copyright file="PlaceOrderUpgrade.cs" company="Softlayer">
// SoftLayer Technologies, Inc.
// </copyright>
// <license>
// http://sldn.softlayer.com/article/License
// </license>
//-----------------------------------------------------------------------
namespace VirtualGuests
{
using System;
using System.Collections.Generic;
class PlaceOrderUpgrade
{
/// <summary>
/// Order an upgrade for Virtual Guest
/// This script orders an upgrade for Virtual Guest, in this case we will upgrade the ram for a Virtual Guest,
/// It uses SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade container and SoftLayer_Product_Order::placeOrder
/// method for it.
/// For more information, review the following links:
/// </summary>
/// <manualPages>
/// http://sldn.softlayer.com/reference/services/SoftLayer_Product_Order/placeOrder
/// http://sldn.softlayer.com/reference/datatypes/SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade/
/// http://sldn.softlayer.com/reference/services/SoftLayer_Product_Item_Price/
/// </manualPages>
static void Main(String [] args)
{
// You SoftLayer username
string username = "set me";
// Your SoftLayer API key.
string apiKey = "set me";
// Define the virtual guest id to place an upgrade
int virtualId = 13115425;
// Creating a connection to the SoftLayer_Product_Order API service and
// bind our API username and key to it.
authenticate authenticate = new authenticate();
authenticate.username = username;
authenticate.apiKey = apiKey;
SoftLayer_Product_OrderService orderService = new SoftLayer_Product_OrderService();
orderService.authenticateValue = authenticate;
// Build a SoftLayer_Product_Item_Price objects with the ids from prices that you want to order.
// You can retrieve them with SoftLayer_Product_Package::getItemPrices method
int[] prices = {
1645
};
List<SoftLayer_Product_Item_Price> pricesList = new List<SoftLayer_Product_Item_Price>();
foreach (var price in prices)
{
SoftLayer_Product_Item_Price newPrice = new SoftLayer_Product_Item_Price();
newPrice.id = price;
newPrice.idSpecified = true;
pricesList.Add(newPrice);
}
// Build SoftLayer_Container_Product_Order_Property object for the upgrade
SoftLayer_Container_Product_Order_Property property = new SoftLayer_Container_Product_Order_Property();
property.name = "MAINTENANCE_WINDOW";
property.value = "NOW";
List<SoftLayer_Container_Product_Order_Property> propertyList = new List<SoftLayer_Container_Product_Order_Property>();
propertyList.Add(property);
// Build SoftLayer_Virtual_Guest object with the id from vsi that you wish to place an upgrade
SoftLayer_Virtual_Guest virtualGuest = new SoftLayer_Virtual_Guest();
virtualGuest.id = virtualId;
virtualGuest.idSpecified = true;
List<SoftLayer_Virtual_Guest> virtualGuests = new List<SoftLayer_Virtual_Guest>();
virtualGuests.Add(virtualGuest);
// Build SoftLayer_Container_Product_Order object containing the information for the upgrade
//SoftLayer_Container_Product_Order orderTemplate = new SoftLayer_Container_Product_Order();
SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade orderTemplate = new SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade();
orderTemplate.containerIdentifier = "SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade";
orderTemplate.prices = pricesList.ToArray();
orderTemplate.properties = propertyList.ToArray();
orderTemplate.virtualGuests = virtualGuests.ToArray();
try
{
// We will check the template for errors, we will use the verifyOrder() method for this.
// Replace it with placeOrder() method when you are ready to order.
SoftLayer_Container_Product_Order verifiedOrder = orderService.verifyOrder(orderTemplate);
Console.WriteLine("Order Verified!");
}
catch (Exception e)
{
Console.WriteLine("Unable to place an upgrade for Virtual Guest: " + e.Message);
}
}
}
}
Let me know if this helps
Regards

redis servicestack client List.Remove(item) does not work

I'm developing a "Task Control System" that will allow its users to enter task description information including when to execute the task and what environment (OS, browser, etc.) the task requires.
The 'controller' saves the description information and schedules the task. When the scheduled time arrives, the scheduler retrieves the task information and 'queues' the task for a remote machine that matches the required environment.
My first cut at this used a relational database to persist the task descriptions and enough history information to track problems (about 2 weeks worth). But this is not a 'big data' problem and the relationships are simple and I need better performance.
So I'm looking for something that offers more performance.
I'm trying to use redis for this, but I'm having some problems. I'm using ServiceStack.Redis version 3.9.71.0 for the client and Redis 2.8.4 is the server.
This sample code is taken from Dan Swain's tutorial. It's updated to work with ServiceStack.Redis client v 3.9.71.0. Much of it works, but 'currentShippers.Remove(lameShipper);' does NOT work.
Can anyone see why that might be?
Thanks
public void ShippersUseCase()
{
using (var redisClient = new RedisClient("localhost"))
{
//Create a 'strongly-typed' API that makes all Redis Value operations to apply against Shippers
var redis = redisClient.As<Shipper>();
//Redis lists implement IList<T> while Redis sets implement ICollection<T>
var currentShippers = redis.Lists["urn:shippers:current"];
var prospectiveShippers = redis.Lists["urn:shippers:prospective"];
currentShippers.Add(
new Shipper
{
Id = redis.GetNextSequence(),
CompanyName = "Trains R Us",
DateCreated = DateTime.UtcNow,
ShipperType = ShipperType.Trains,
UniqueRef = Guid.NewGuid()
});
currentShippers.Add(
new Shipper
{
Id = redis.GetNextSequence(),
CompanyName = "Planes R Us",
DateCreated = DateTime.UtcNow,
ShipperType = ShipperType.Planes,
UniqueRef = Guid.NewGuid()
});
var lameShipper = new Shipper
{
Id = redis.GetNextSequence(),
CompanyName = "We do everything!",
DateCreated = DateTime.UtcNow,
ShipperType = ShipperType.All,
UniqueRef = Guid.NewGuid()
};
currentShippers.Add(lameShipper);
Dump("ADDED 3 SHIPPERS:", currentShippers);
currentShippers.Remove(lameShipper);
.
.
.
}
}
Fixed the problem by adding these overrides to the 'Shipper' class:
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
var input = obj as Shipper;
return input != null && Equals(input);
}
public bool Equals(Shipper other)
{
return other != null && (Id.Equals(other.Id));
}
public override int GetHashCode()
{
return (int)Id;
}
This working example shows how to implement List<>.Contains, List<>.Find, and List<>.Remove. Once applied to the 'Shipper' class the problem was solved!

Categories

Resources