Make Swagger output the description of an IRouteConstraint - asp.net-core

I have a route with a custom IRouteConstraint.
Swagger generates the parameter section but the description field is always empty. For other parameters I get the description from the XML comments correctly.
The only workaround I found so far is adding a Operation filter and set the description there for the
foreach ( var parameter in operation.Parameters.OfType<NonBodyParameter>() )
{
if (parameter.Name == RouteConstraint.Name)
{
parameter.Description = GetConstraintDescription();
}
Any way to instruct swagger to get the description from the XML comments for IRouteConstraints.

I'm afraid that the only "easy way" is what you already implemented (an Operation filter)
I was looking at the XML description that your project outputs
<?xml version="1.0"?>
<doc>
<assembly>
<name>SwaggerWeb</name>
</assembly>
<members>
<member name="M:SwaggerWeb.Controllers.ValuesController.Get(System.Int32)">
<summary> Get by ID </summary>
<param name="id">The value ID</param>
<returns></returns>
</member>
<member name="T:SwaggerWeb.SectorRouteConstraint">
<summary> Sector constraint </summary>
</member>
</members>
</doc>
You can try make your filter a bit more generic and fish the description out of the XML, but other than that I do not see any other way.

My current solution is this class, based on the Swashbuckle XmlCommentsOperationFilter.
public class RouteConstraintXmlDocsOperationFilter:IOperationFilter
{
private readonly XPathNavigator _xmlNavigator;
private const string MemberXPath = "/doc/members/member[#name='{0}']";
private const string SummaryXPath = "summary";
public RouteConstraintXmlDocsOperationFilter(string filePath)
{
XPathDocument xmlDoc = new XPathDocument(filePath);
_xmlNavigator = xmlDoc.CreateNavigator();
}
public void Apply(Operation operation, OperationFilterContext context)
{
ApplyConstraintsXmlToActionParameters(operation.Parameters, context.ApiDescription);
}
private void ApplyConstraintsXmlToActionParameters(IList<IParameter> parameters, ApiDescription apiDescription)
{
var nonBodyParameters = parameters.OfType<NonBodyParameter>();
foreach (var parameter in nonBodyParameters)
{ // Check for a corresponding action parameter?
var actionParameter = apiDescription.ParameterDescriptions.FirstOrDefault(p =>
parameter.Name.Equals(p.Name, StringComparison.OrdinalIgnoreCase));
if (actionParameter == null) continue;
if (!actionParameter.RouteInfo.Constraints.Any()) continue;
var constraintType = actionParameter.RouteInfo.Constraints.FirstOrDefault().GetType();
var commentIdForType = XmlCommentsIdHelper.GetCommentIdForType(constraintType);
var constraintSummaryNode = _xmlNavigator
.SelectSingleNode(string.Format(MemberXPath, commentIdForType))
?.SelectSingleNode(SummaryXPath);
if (constraintSummaryNode != null)
{
parameter.Description = XmlCommentsTextHelper.Humanize(constraintSummaryNode.InnerXml);
}
}
}
}
To set it up:
services.AddSwaggerGen(o =>
{
var fileName = GetType().GetTypeInfo().Module.Name.Replace(".dll", ".xml").Replace(".exe", ".xml");
o.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, fileName));
o.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
o.OperationFilter<RouteConstraintXmlDocsOperationFilter>(Path.Combine(AppContext.BaseDirectory, fileName));
})

Related

Remove route from RouteCollection in Asp.Net Core and add new with same route name (nopCommerce-4.00)

I want to remove existing route from RouteCollection and want to add new route with same route name in nopCommerce 4.00 via plugin
Existing route name:
//home page
routeBuilder.MapLocalizedRoute("HomePage", "",
new { controller = "Home", action = "Index" });
I Want to replace it with
routeBuilder.MapLocalizedRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage" });
I tried several ways but not get any luck.
In my case, I have to replace the robots.txt generation.
I created a new public controller in my plugin, and I copy the original action here:
public class MiscCommonController : BasePublicController
{
#region Fields
private readonly ICommonModelFactory _commonModelFactory;
#endregion Fields
#region Ctor
public MiscCommonController(
ICommonModelFactory commonModelFactory
)
{
this._commonModelFactory = commonModelFactory;
}
#endregion Ctor
#region Methods
//robots.txt file
//available even when a store is closed
[CheckAccessClosedStore(true)]
//available even when navigation is not allowed
[CheckAccessPublicStore(true)]
public virtual IActionResult RobotsTextFile()
{
var robotsFileContent = _commonModelFactory.PrepareRobotsTextFile();
return Content(robotsFileContent, MimeTypes.TextPlain);
}
#endregion Methods
}
After this I create a RouteProvider for my plugin, and I replaced the original route to my own one.
public partial class RouteProvider : IRouteProvider
{
/// <summary>
/// Gets a priority of route provider
/// </summary>
public int Priority => -1;
/// <summary>
/// Register routes
/// </summary>
/// <param name="routeBuilder">Route builder</param>
public void RegisterRoutes(IRouteBuilder routeBuilder)
{
Route route = null;
foreach (Route item in routeBuilder.Routes)
{
if (item.Name == "robots.txt")
{
route = item;
break;
}
}
if (route != null) routeBuilder.Routes.Remove(route);
routeBuilder.MapRoute(
"robots.txt",
"robots.txt",
new { controller = "MiscCommon", action = "RobotsTextFile" }
);
}
}
That's all.
After this implementation, the routing works fine, and the get request landed in my own controller, which is act like the original.
Now, I can replace the generation logic with my own.
I hope it helps.
in the RouteProvider.cs of your plugin write these codes (based on your names):
var lastExistingRoute= routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "HomePage");
routeBuilder.Routes.Remove(lastExistingRoute);
routeBuilder.MapRoute("HomePage", "",
new { controller = "CustomPage", action = "Homepage", });
and the below codes worked for myself version 4.20:
var lastDownloadRoute=routeBuilder.Routes.FirstOrDefault(x => ((Route)x).Name == "GetDownload");
routeBuilder.Routes.Remove(lastDownloadRoute);
routeBuilder.MapRoute("GetDownload", "download/getdownload/{guid}/{agree?}",
new { controller = "AzTechProduct", action = "GetPayed", });
There are two potential ways to deal with this in nopCommerce 4.3 that I see with a quick examination of the code.
First, you could create an IRouteProvider, add your route that has the same signature as the one you wish to 'delete' and make sure the Priority on the provider is greater than 1.
Doing this will basically override the default route built into Nop. This is my preferred method.
public partial class RouteProvider: IRouteProvider
{
public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
{
var pattern = string.Empty;
if (DataSettingsManager.DatabaseIsInstalled)
{
var localizationSettings = endpointRouteBuilder.ServiceProvider.GetRequiredService<LocalizationSettings>();
if (localizationSettings.SeoFriendlyUrlsForLanguagesEnabled)
{
var langservice = endpointRouteBuilder.ServiceProvider.GetRequiredService<ILanguageService>();
var languages = langservice.GetAllLanguages().ToList();
pattern = "{language:lang=" + languages.FirstOrDefault().UniqueSeoCode + "}/";
}
}
// Handle the standard request
endpointRouteBuilder.MapControllerRoute("Wishlist", pattern + "wishlist/{customerGuid?}",
new { controller = "MyShoppingCart", action = "Wishlist" });
return;
}
public int Priority => 100;
}
The key to the code above is the Priority value. This route will get added to the list first and will therefore take precedence over the default route. Using this technique eliminates the need to delete the default route.
The second possible method turns out to not work because the endpointRouteBuilder.DataSources[n].Endpoints collection is read only. So, as far as I know, you can't remove mappings from that list after they have been added.

typemock test not working

public class GetDatasourceDependencies : BaseProcessor
{
/// <summary>
/// The process.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
public override void Process(GetDependenciesArgs context)
{
Assert.IsNotNull(context.IndexedItem, "indexed item");
Assert.IsNotNull(context.Dependencies, "dependencies");
Item item = (context.IndexedItem as SitecoreIndexableItem);
if (item != null)
{
var layoutLinks = Globals.LinkDatabase.GetReferrers(item, FieldIDs.LayoutField);
var sourceUris = layoutLinks.Select(l => l.GetSourceItem().Uri).Where(uri => uri != null && uri != item.Uri).Distinct();
context.Dependencies.AddRange(sourceUris.Select(x => (SitecoreItemUniqueId)x));
}
}
}
How do I write a test with typock for this. I am very new to typemock and have written something like this. I understand that i need to mock the args and context but as the method is returning nothing back, how do i test it.
My test should be success only if the context.dependents have some values.
[Test]
public void GetIndexingDependencies_Calls()
{
var indexable = Isolate.Fake.Instance<IIndexable>();
var fake = Isolate.Fake.Instance<GetDependenciesArgs>();
var context = Isolate.Fake.Instance<GetDatasourceDependencies>();
var obj = new GetDatasourceDependencies();
Isolate.Verify.WasCalledWithAnyArguments(() => context.Process(fake));
Isolate.WhenCalled(() => fake.IndexedItem).WillReturn(indexable);
//Isolate.WhenCalled(() => fake.Dependencies.Count).WillReturn(2);
}
Disclaimer, I work at Typemock.
You can use a real collection for context.Dependencies and assert that some items are actually added.
To achieve this you should replace the collection that Globals returns and make sure that linq can process it as you expect (I just returned the same collection from the linq query for the sake of the example).
Your test should look something like this:
public void GetIndexingDependencies_Calls()
{
//Arrange
var fakeContext = Isolate.Fake.Instance<GetDependenciesArgs>();
fakeContext.Dependencies = new List<SitecoreItemUniqueId>();
var itemList = new List<SomeItem> { new SomeItem(), new SomeItem() };
Isolate.WhenCalled(() => Globals.LinkDatabase.GetReferrers(null, null)).WillReturn(itemList);
Isolate.WhenCalled(() => itemList.Select(l => l.GetSourceItem().Uri).Where(uri => true).Distinct()).WillReturn(itemList);
//ACT
var underTest = new GetDatasourceDependencies();
underTest.Process(fakeContext);
//ASSERT
Assert.AreEqual(2, fakeContext.Dependencies.Count);
}
Some more points:
Don't fake whatever you're testing, in this case it's GetDatasourceDependencies. If it's faked it will not really be called and not be tested.
Don't write asserts\verify before the executing the code that you're trying to test, in this case context.Process(fake) wasn't called before verify.

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

C# Compact-Framework friendly command line parser

I read this question: Command Line Parser for .NET.
I thought that was what I was looking for, but the library Command Line Parser Library is not Compact framework friendly...
I REALLY don't want to write a CL parser and I have been drifting away from the real purpose of my little app because of this unfortunate trial.
Does someone know of a library that fits the compact-framework? (preferably with simplicity and functionality like the one mentioned above)
Does not matter whether version 2 or 3.5
I developed this framework, maybe it helps:
The SysCommand is a powerful cross-platform framework, to develop Console Applications in .NET. Is simple, type-safe, and with great influences of the MVC pattern.
https://github.com/juniorgasparotto/SysCommand
namespace Example.Initialization.Simple
{
using SysCommand.ConsoleApp;
public class Program
{
public static int Main(string[] args)
{
return App.RunApplication();
}
}
// Classes inheriting from `Command` will be automatically found by the system
// and its public properties and methods will be available for use.
public class MyCommand : Command
{
public void Main(string arg1, int? arg2 = null)
{
if (arg1 != null)
this.App.Console.Write(string.Format("Main arg1='{0}'", arg1));
if (arg2 != null)
this.App.Console.Write(string.Format("Main arg2='{0}'", arg2));
}
public void MyAction(bool a)
{
this.App.Console.Write(string.Format("MyAction a='{0}'", a));
}
}
}
Tests:
// auto-generate help
$ my-app.exe help
// method "Main" typed
$ my-app.exe --arg1 value --arg2 1000
// or without "--arg2"
$ my-app.exe --arg1 value
// actions support
$ my-app.exe my-action -a
This is what I'm using. I borrowed it from somewhere, but not sure where:
using System.Collections.Specialized;
using System.Text.RegularExpressions;
/// <summary>
/// Parses the command line arguments into a name/value collection
/// </summary>
public class CommandLineArgumentParser
{
#region Fields
private StringDictionary parameters;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CommandLineArgumentParser"/> class.
/// </summary>
/// <param name="args">command-line arguments
/// </param>
public CommandLineArgumentParser(string[] args)
{
this.parameters = new StringDictionary();
Regex spliter = new Regex(#"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex remover = new Regex(#"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
string parameter = null;
string[] parts;
// Valid parameters forms:
// {-,/,--}param{ ,=,:}((",')value(",'))
// Examples:
// -param1 value1 --param2 /param3:"Test-:-work"
// /param4=happy -param5 '--=nice=--'
foreach (string txt in args)
{
// Look for new parameters (-,/ or --) and a
// possible enclosed value (=,:)
parts = spliter.Split(txt, 3);
switch (parts.Length)
{
// Found a value (for the last parameter
// found (space separator))
case 1:
if (parameter != null)
{
if (!this.parameters.ContainsKey(parameter))
{
parts[0] = remover.Replace(parts[0], "$1");
this.parameters.Add(parameter, parts[0]);
}
parameter = null;
}
// else Error: no parameter waiting for a value (skipped)
break;
// Found just a parameter
case 2:
// The last parameter is still waiting.
// With no value, set it to true.
if (parameter != null)
{
if (!this.parameters.ContainsKey(parameter))
{
this.parameters.Add(parameter, "true");
}
}
parameter = parts[1];
break;
// Parameter with enclosed value
case 3:
// The last parameter is still waiting.
// With no value, set it to true.
if (parameter != null)
{
if (!this.parameters.ContainsKey(parameter))
{
this.parameters.Add(parameter, "true");
}
}
parameter = parts[1];
// Remove possible enclosing characters (",')
if (!this.parameters.ContainsKey(parameter))
{
parts[2] = remover.Replace(parts[2], "$1");
this.parameters.Add(parameter, parts[2]);
}
parameter = null;
break;
}
}
// In case a parameter is still waiting
if (parameter != null)
{
if (!this.parameters.ContainsKey(parameter))
{
this.parameters.Add(parameter, "true");
}
}
}
#endregion
#region Properties
/// <summary>
/// Gets a count of command line arguments
/// </summary>
public int Count
{
get
{
return this.parameters.Count;
}
}
/// <summary>
/// Gets the value with the given parameter name
/// </summary>
/// <param name="param">name of the parameter</param>
/// <returns>the value of the parameter</returns>
public string this[string param]
{
get
{
return this.parameters[param];
}
}
#endregion
}
http://commandline.codeplex.com/ I've used this so many times I've lost count. Maybe it works for CE. If not, it'll provide a fantastic starting point.