Serialize a FeatureClass to XML in ESRI ArcGIS - arcgis

How can I serialize an IFeatureClass object to XML?
There are some resources for using IXMLSerializer on other ArcObjects, but that won't work for IFeatureClass because it doesn't implement ISerializable.

I've actual found my own answer to this question. I'm posting this question and answer here for the benefit of others and for feedback/critique on my approach.
IFeatureClass cannot be serialized directly, but IRecordSet2 can be. So the first step is implementing a method to convert IFeatureClass to IRecordSet2:
private static IRecordSet2 ConvertToRecordset(IFeatureClass fc)
{
IRecordSet recSet = new RecordSetClass();
IRecordSetInit recSetInit = recSet as IRecordSetInit;
recSetInit.SetSourceTable(fc as ITable, null);
return (IRecordSet2) recSetInit;
}
Then it's easy to use IXMLSerializer to get XML:
public static XElement StoreAsXml(IFeatureClass fc)
{
// Can't serialize a feature class directly, so convert
// to recordset first.
IRecordSet2 recordset = ConvertToRecordset(fc);
IXMLSerializer xmlSer = new XMLSerializerClass();
string sXml = xmlSer.SaveToString(recordset, null, null);
return XElement.Parse(sXml);
}
However, when you convert to IRecordSet2, you lose the feature class name, so when writing to a file, I add an element to the XML to hold the feature class name:
public static void StoreToFile(IFeatureClass fc, string filePath)
{
XElement xdoc = StoreAsXml(fc);
XElement el = new XElement("FeatureClass", new XAttribute( "name", fc.AliasName ),
xdoc);
el.Save(filePath);
}
Now, just reverse the process to read the XML into a feature class. Remember that an element was added to store the feature class name:
public static IFeatureClass RetreiveFromFile(string filepath)
{
XElement xdoc = XElement.Load(filepath);
string sName = xdoc.FirstAttribute.Value;
XNode recordset = xdoc.FirstNode;
return RetreiveFromXml(recordset, sName);
}
Simple de-serialization using IXMLSerializer to get a IRecordSet2:
public static IFeatureClass RetreiveFromXml(XNode node, string sName)
{
IXMLSerializer xmlDeSer = new XMLSerializerClass();
IRecordSet2 recordset = (IRecordSet2)xmlDeSer.LoadFromString(node.ToString(), null, null);
return ConvertToFeatureClass(recordset, sName);
}
This was the tricky part. I'm open to suggestions on how to improve this ... covert the IRecordSet2 object into an IFeatureClass:
private static IFeatureClass ConvertToFeatureClass(IRecordSet2 rs, string sName)
{
IWorkspaceFactory pWSFact = new ShapefileWorkspaceFactory();
string sTempPath = Path.GetTempPath();
IFeatureWorkspace pFWS = (IFeatureWorkspace)pWSFact.OpenFromFile( sTempPath, 0);
// Will fail (COM E_FAIL) if the dataset already exists
DeleteExistingDataset(pFWS, sName);
IFeatureClass pFeatClass = null;
pFeatClass = pFWS.CreateFeatureClass(sName, rs.Fields, null, null, esriFeatureType.esriFTSimple,
"SHAPE", "");
// Copy incoming record set table to new feature class's table
ITable table = (ITable) pFeatClass;
table = rs.Table;
IFeatureClass result = table as IFeatureClass;
// It will probably work OK without this, but it makes the XML match more closely
IClassSchemaEdit3 schema = result as IClassSchemaEdit3;
schema.AlterAliasName(sName);
schema.AlterFieldAliasName("FID", "");
schema.AlterFieldModelName("FID", "");
schema.AlterFieldAliasName("Shape", "");
schema.AlterFieldModelName("Shape", "");
// If individual fields need to be edited, do something like this:
// int nFieldIndex = result.Fields.FindField("Shape");
// IFieldEdit2 field = (IFieldEdit2)result.Fields.get_Field(nFieldIndex);
// Cleanup
DeleteExistingDataset(pFWS, sName);
return table as IFeatureClass;
}
Finally, a utility method for deleting an existing dataset. This was copy/pasted from somewhere, but I can't remember the source.
public static void DeleteExistingDataset(IFeatureWorkspace pFWS, string sDatasetName)
{
IWorkspace pWS = (IWorkspace)pFWS;
IEnumDatasetName pEDSN = pWS.get_DatasetNames(esriDatasetType.esriDTFeatureClass);
bool bDatasetExists = false;
pEDSN.Reset();
IDatasetName pDSN = pEDSN.Next();
while (pDSN != null)
{
if (pDSN.Name == sDatasetName)
{
bDatasetExists = true;
break;
}
pDSN = pEDSN.Next();
}
if (bDatasetExists)
{
IFeatureClass pFC = pFWS.OpenFeatureClass(sDatasetName);
IDataset pDataset = (IDataset)pFC;
pDataset.Delete();
}
}

Related

Saving and Loading file definitions while using <generic> version of FileEngine

I've successfully use the SaveToXml and LoadFromXml methods on the ClassBuilder class to store and retrieve the file definitions while using the Standard version of the File Helper Engine.
However I'd really prefer to use the generic version of the File Helper Engine. In other words I'd like to instatiate the engine like so:
var eng = new DelimitedFileEngine<OutputClass>(params....);
OutputClass[] records = eng.ReadFile("Sample.csv");
So my results are strongly typed and not just an array of objects.
Does this save and load functionality exist for the generic file helper engine?
Sure, it works exactly as you'd expect.
[DelimitedRecord("|")]
public class OutputClass
{
public string First { get; set; }
public string Second { get; set; }
}
class Program
{
static void Main(string[] args)
{
var eng = new DelimitedFileEngine<OutputClass>();
// To read from a file use ReadFile()
//OutputClass[] records = eng.ReadFile("Sample.csv");
// Or to read from a string use ReadString()
OutputClass[] records = eng.ReadString("First|Second");
Debug.Assert(records.Length == 1);
Debug.Assert(records[0].First == "First");
Debug.Assert(records[0].Second == "Second");
Console.WriteLine("All OK");
Console.ReadKey();
}
}
Edit:
Based on your comment below, you want to map the results from your XML class to a concrete C# object. The easiest way is to use read into a DataTable and map the fields to the C# object.
var cb = new DelimitedClassBuilder(nameof(OutputClass), "|");
cb.AddField("First", typeof(string));
cb.AddField("Second", typeof(string));
var xmlString = cb.SaveToXmlString();
var outputClass = DelimitedClassBuilder.LoadFromXmlString(xmlString);
var eng = new FileHelperEngine(outputClass.CreateRecordClass());
OutputClass[] records = eng
.ReadStringAsDT("First|Second")
.Rows.OfType<DataRow>()
.Select(x => new OutputClass() {
First = x.Field<string>("First"),
Second = x.Field<string>("Second")
})
.ToArray();

How to pass bean class to a test method in testng?

I have an Excel Util which reads all the data from excel sheet. The excel sheet has 10 columns like time, sourceType, tid, message, severity,
lastModify, entityName, operationType, replayId, recordIds.
My DataProvider has code something like this which returns all the 10 columns and their values.
#DataProvider(name="googleData")
public static Object[][] testData() {
String filePath = "/Users/TestUser/Workspace/FixProject/ExcelCheck/src/test/resources/excelreader.xlsx";
Object[][] arrayObject = excelFileUtils.getExcelData(filePath, "excelreader");
return arrayObject;
}
In My TestMethod, I have to pass all these 10 columns or else it wont allow me to run. Instead I want to create a Bean Class and pass something like this to my test method
#Test(dataProvider = "googleData", dataProviderClass = DataProviders.class)
public void testGoogleData(BeanClass object) {
System.out.println(object.getTid());
}
How do we achieve this?
Using the dataProvider you have, your test method is going to run 10 times for each object in the array.
What you can do is to create an object, convert your dataProvider into that object and than use it your test method code.
Object myDataHelper = null;
#Test()
public void testGoogleData(BeanClass object) {
myDataHelper = convertDataProviderToObject();
// use it here in a for/for each loop
System.out.println(object.getTid());
}
public static Object[][] read_excel(String Sheet_Name) throws Exception
{
File obj = new File("./src/main/java/com/Demo/TestData/Test_Data.xlsx");
FileInputStream fis = new FileInputStream(obj);
XSSFWorkbook wb = new XSSFWorkbook(fis);
XSSFSheet sheet = wb.getSheet(Sheet_Name);
int row_number = sheet.getLastRowNum();
int column_number = sheet.getRow(0).getLastCellNum();
Object data[][] = new Object[row_number][column_number];
wb.close();
for(int i=0; i<row_number; i++)
{
for(int j=0; j<column_number; j++)
{
data[i][j] = sheet.getRow(i + 1).getCell(j).toString();
}
}
return data;
}
#DataProvider
public Object[][] getDataFromExcel() throws Exception
{ Object[][] data = Utility.read_excel("Admin_Credentials");//Sheet name
return data;
}
#Test(dataProvider="getDataFromExcel")
It is supported in QAF-TestNG extension. You can have one or more complex object argument in your test method while using inbuilt or custom data provider. For excel your code may look like below:
#QAFDataProvider(dataFile = "resources/data/googletestdata.xls")
#Test
public void testGoogleData(BeanClass object) {
System.out.println(object.getTid());
}
For custom data provider it may look like below:
#QAFDataProvider
#Test(dataProvider = "googleData", dataProviderClass = DataProviders.class)
public void testGoogleData(BeanClass object) {
System.out.println(object.getTid());
}
You need to make sure that,properties name in your bean class must match column names (in any order). When using custom data provider you need to return Iterator for List of Map<String, Object> or Object[][] having Map, refer few example if you required to create custom data provider.

How can I connect my databse by using LINQtoSPARQL for writing linq based services?

The following is the code of TestDataProvider class in LINQtoSPARQL (githublinkk). Here, data is taken from string or file.
namespace LINQtoSPARQLSpace.Tests
{
public static class TestDataProvider
{
public static ISPARQLQueryable<T> GetQuerable<T>(string data,
bool autoquotation = true,
bool treatUri = true,
IEnumerable<Prefix> prefixes = null,
bool skipTriplesWithEmptyObject = false,
bool mindAsterisk = false,
bool useStore = false,
string defaultGraphUri = "http://test.org/defaultgraph")
{
DynamicSPARQLSpace.dotNetRDF.Connector connector = null;
if (useStore)
{
var store = new VDS.RDF.TripleStore();
store.LoadFromString(data);
connector = new Connector(new InMemoryDataset(store, new Uri(defaultGraphUri)));
}
else
{
var graph = new VDS.RDF.Graph();
graph.LoadFromFile(data);
connector = new Connector(new InMemoryDataset(graph));
}
dynamic dyno = DynamicSPARQL.CreateDyno(connector.GetQueryingFunction(),
updateFunc: connector.GetUpdateFunction(),
autoquotation: autoquotation,
treatUri: treatUri,
prefixes:prefixes,
skipTriplesWithEmptyObject:skipTriplesWithEmptyObject,
mindAsterisk:mindAsterisk);
return new SPARQLQuery<T>(dyno);
}
}
}
Now, I want to connect my database (GraphDB ontotext). for now, I am able to connect my database with the help of following lines:
Options.ForceHttpBasicAuth = true;
var store2 = new VDS.RDF.Query.SparqlRemoteEndpoint(new Uri("http://localhost:7200/repositories/786"));
store2.SetCredentials("admin", "admin");
Now, how can I pass this SparqlRemoteEndpoint object to Connector class object? am I going in the right way to connect?
Please Help.

How to add help in Web API for external library classes

Good morning,
I have a solution consisting of two projects. One is a class library, containing common classes that will be used in other projects. The other is a WebAPI 2.1 project.
I am generating the help files for the API by using the automatic help page generator, but I've noticed that when it references classes in the Common project, it doesn't use the summaries.
Is there any way of making it do this? I've searched online but I can't find any solution to this. I've also tried installing the help page generator in the Common project, but to no avail.
I had the same problem and this is just because the documentation provider takes only one xml document which is the one generated from current project (if you followed the instructions you may remember adding:
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/[YOUR XML DOCUMENT]"));
The rest of your classes and their metadata is added to a different xml document. What I did is I modified the xml documentation provider to accept multiple xml document path and search through each document for metadata related to the class been enquired about. You would need to add the xml document from the various dlls you are referencing but this definitely solved my issue. See below for the variation of the XmlDocumentationProvider:
public class XmlMultiDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
private List<XPathNavigator> _documentNavigator;
private const string TypeExpression = "/doc/members/member[#name='T:{0}']";
private const string MethodExpression = "/doc/members/member[#name='M:{0}']";
private const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
private const string FieldExpression = "/doc/members/member[#name='F:{0}']";
private const string ParameterExpression = "param[#name='{0}']";
/// <summary>
/// Initializes a new instance of the <see cref="XmlDocumentationProvider"/> class.
/// </summary>
/// <param name="documentPath">The physical path to XML document.</param>
public XmlMultiDocumentationProvider(params string[] documentPath)
{
if (documentPath == null)
{
throw new ArgumentNullException("documentPath");
}
_documentNavigator = new List<XPathNavigator>();
foreach (string s in documentPath)
{
XPathDocument xpath = new XPathDocument(s);
_documentNavigator.Add(xpath.CreateNavigator());
}
}
public string GetDocumentation(HttpControllerDescriptor controllerDescriptor)
{
XPathNavigator typeNode = GetTypeNode(controllerDescriptor.ControllerType);
return GetTagValue(typeNode, "summary");
}
public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor)
{
XPathNavigator methodNode = GetMethodNode(actionDescriptor);
return GetTagValue(methodNode, "summary");
}
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
return null;
}
public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor)
{
XPathNavigator methodNode = GetMethodNode(actionDescriptor);
return GetTagValue(methodNode, "returns");
}
public string GetDocumentation(MemberInfo member)
{
string memberName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(member.DeclaringType), member.Name);
string expression = member.MemberType == MemberTypes.Field ? FieldExpression : PropertyExpression;
string selectExpression = String.Format(CultureInfo.InvariantCulture, expression, memberName);
XPathNavigator propertyNode = null;
foreach(XPathNavigator navigator in _documentNavigator )
{
XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
if (temp != null)
{
propertyNode = temp;
break;
}
}
return GetTagValue(propertyNode, "summary");
}
public string GetDocumentation(Type type)
{
XPathNavigator typeNode = GetTypeNode(type);
return GetTagValue(typeNode, "summary");
}
private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor)
{
ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor;
if (reflectedActionDescriptor != null)
{
string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo));
foreach (XPathNavigator navigator in _documentNavigator)
{
XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
if (temp != null)
{
return temp;
}
}
}
return null;
}
private static string GetMemberName(MethodInfo method)
{
string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", GetTypeName(method.DeclaringType), method.Name);
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 0)
{
string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray();
name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames));
}
return name;
}
private static string GetTagValue(XPathNavigator parentNode, string tagName)
{
if (parentNode != null)
{
XPathNavigator node = parentNode.SelectSingleNode(tagName);
if (node != null)
{
return node.Value.Trim();
}
}
return null;
}
private XPathNavigator GetTypeNode(Type type)
{
string controllerTypeName = GetTypeName(type);
string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName);
foreach (XPathNavigator navigator in _documentNavigator)
{
XPathNavigator temp = navigator.SelectSingleNode(selectExpression);
if (temp != null)
{
return temp;
}
}
return null;
}
private static string GetTypeName(Type type)
{
string name = type.FullName;
if (type.IsGenericType)
{
// Format the generic type name to something like: Generic{System.Int32,System.String}
Type genericType = type.GetGenericTypeDefinition();
Type[] genericArguments = type.GetGenericArguments();
string genericTypeName = genericType.FullName;
// Trim the generic parameter counts from the name
genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray();
name = String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", genericTypeName, String.Join(",", argumentTypeNames));
}
if (type.IsNested)
{
// Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax.
name = name.Replace("+", ".");
}
return name;
}
}
You can get the idea or simply use the whole class at your discretion. Just remember to replace in your HelpPageConfig -> SetDocumentationProvider call the class name and add the path to the various xml documents.

Web API Help pages - customizing Property documentation

I have my web api and I added the web api help pages to auto-generate my documentation. It's working great for methods where my parameters are listed out, but I have a method like this:
public SessionResult PostLogin(CreateSessionCommand request)
And, on my help page, it is only listing the command parameter in the properties section. However, in the sample request section, it lists out all of the properties of my CreateSessionCommand class.
Parameters
Name | Description | Additional information
request | No documentation available. | Define this parameter in the request body.
I would like it instead to list all of the properties in my CreateSessionCommand class. Is there an easy way to do this?
So, I managed to devise a workaround for this problem, in case anyone is interested.
In HelpPageConfigurationExtensions.cs I added the following extension method:
public static void AlterApiDescription(this ApiDescription apiDescription, HttpConfiguration config)
{
var docProvider = config.Services.GetDocumentationProvider();
var addParams = new List<ApiParameterDescription>();
var removeParams = new List<ApiParameterDescription>();
foreach (var param in apiDescription.ParameterDescriptions)
{
var type = param.ParameterDescriptor.ParameterType;
//string is some special case that is not a primitive type
//also, compare by full name because the type returned does not seem to match the types generated by typeof
bool isPrimitive = type.IsPrimitive || String.Compare(type.FullName, typeof(string).FullName) == 0;
if (!isPrimitive)
{
var properties = from p in param.ParameterDescriptor.ParameterType.GetProperties()
let s = p.SetMethod
where s.IsPublic
select p;
foreach (var property in properties)
{
var documentation = docProvider.GetDocumentation(new System.Web.Http.Controllers.ReflectedHttpParameterDescriptor()
{
ActionDescriptor = param.ParameterDescriptor.ActionDescriptor,
ParameterInfo = new CustomParameterInfo(property)
});
addParams.Add(new ApiParameterDescription()
{
Documentation = documentation,
Name = property.Name,
Source = ApiParameterSource.FromBody,
ParameterDescriptor = param.ParameterDescriptor
});
}
//since this is a complex type, select it to be removed from the api description
removeParams.Add(param);
}
}
//add in our new items
foreach (var item in addParams)
{
apiDescription.ParameterDescriptions.Add(item);
}
//remove the complex types
foreach (var item in removeParams)
{
apiDescription.ParameterDescriptions.Remove(item);
}
}
And here is the Parameter info instanced class I use
internal class CustomParameterInfo : ParameterInfo
{
public CustomParameterInfo(PropertyInfo prop)
{
base.NameImpl = prop.Name;
}
}
Then, we call the extension in another method inside the extensions class
public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
{
object model;
string modelId = ApiModelPrefix + apiDescriptionId;
if (!config.Properties.TryGetValue(modelId, out model))
{
Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
if (apiDescription != null)
{
apiDescription.AlterApiDescription(config);
HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
model = GenerateApiModel(apiDescription, sampleGenerator);
config.Properties.TryAdd(modelId, model);
}
}
return (HelpPageApiModel)model;
}
The comments that are used for this must be added to the controller method and not the properties of the class object. This might be because my object is part of an outside library
this should go as an addition to #Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
{
const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;
string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name);
XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
if (methodNode != null)
{
return methodNode.Value.Trim();
}
}
else
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
}
return null;
}
and CustomParameterInfo class should keep property info as well:
internal class CustomParameterInfo : ParameterInfo
{
public PropertyInfo Prop { get; private set; }
public CustomParameterInfo(PropertyInfo prop)
{
Prop = prop;
base.NameImpl = prop.Name;
}
}
This is currently not supported out of the box. Following bug is kind of related to that:
http://aspnetwebstack.codeplex.com/workitem/877