HI everyone i am beginner in C# so i need help. I am making C# windows application forms and using Class and used a Button to call a class but i dont know how to call a class by clicking button. help will be appreciated. i am creating another form and only 1 button it contains, the DSN NAME i want to create is "Fassets" Database name is also Fassets
enter image description here
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace dsn
{
public static class Class1
{
private const string ODBC_INI_REG_PATH = "SOFTWARE\\ODBC\\ODBC.INI\\";
private const string ODBCINST_INI_REG_PATH = "SOFTWARE\\ODBC\\ODBCINST.INI\\";
/// <summary>
/// Creates a new DSN entry with the specified values. If the DSN exists, the values are updated.
/// </summary>
/// <param name="dsnName">Name of the DSN for use by client applications</param>
/// <param name="description">Description of the DSN that appears in the ODBC control panel applet</param>
/// <param name="server">Network name or IP address of database server</param>
/// <param name="driverName">Name of the driver to use</param>
/// <param name="trustedConnection">True to use NT authentication, false to require applications to supply username/password in the connection string</param>
/// <param name="database">Name of the datbase to connect to</param>
public static void CreateDSN(string dsnName, string description, string server, string driverName, bool trustedConnection, string database)
{
// Lookup driver path from driver name
var driverKey = Registry.LocalMachine.CreateSubKey(ODBCINST_INI_REG_PATH + driverName);
if (driverKey == null) throw new Exception(string.Format("ODBC Registry key for driver '{0}' does not exist", driverName));
string driverPath = driverKey.GetValue("Driver").ToString();
// Add value to odbc data sources
var datasourcesKey = Registry.LocalMachine.CreateSubKey(ODBC_INI_REG_PATH + "ODBC Data Sources");
if (datasourcesKey == null) throw new Exception("ODBC Registry key for datasources does not exist");
datasourcesKey.SetValue(dsnName, driverName);
// Create new key in odbc.ini with dsn name and add values
var dsnKey = Registry.LocalMachine.CreateSubKey(ODBC_INI_REG_PATH + dsnName);
if (dsnKey == null) throw new Exception("ODBC Registry key for DSN was not created");
dsnKey.SetValue("Fassets", database);
dsnKey.SetValue("Description", description);
dsnKey.SetValue("driverPath", driverPath);
dsnKey.SetValue("LastUser", Environment.UserName);
dsnKey.SetValue("Server", server);
dsnKey.SetValue("Database", database);
dsnKey.SetValue("Trusted_Connection", trustedConnection ? "Yes" : "No");
}
/// <summary>
/// Removes a DSN entry
/// </summary>
/// <param name="dsnName">Name of the DSN to remove.</param>
public static void RemoveDSN(string dsnName)
{
// Remove DSN key
Registry.LocalMachine.DeleteSubKeyTree(ODBC_INI_REG_PATH + dsnName);
// Remove DSN name from values list in ODBC Data Sources key
var datasourcesKey = Registry.LocalMachine.CreateSubKey(ODBC_INI_REG_PATH + "ODBC Data Sources");
if (datasourcesKey == null) throw new Exception("ODBC Registry key for datasources does not exist");
datasourcesKey.DeleteValue(dsnName);
}
///<summary>
/// Checks the registry to see if a DSN exists with the specified name
///</summary>
///<param name="dsnName"></param>
///<returns></returns>
public static bool DSNExists(string dsnName)
{
var driversKey = Registry.LocalMachine.CreateSubKey(ODBCINST_INI_REG_PATH + "ODBC Drivers");
if (driversKey == null) throw new Exception("ODBC Registry key for drivers does not exist");
return driversKey.GetValue(dsnName) != null;
}
///<summary>
/// Returns an array of driver names installed on the system
///</summary>
///<returns></returns>
public static string[] GetInstalledDrivers()
{
var driversKey = Registry.LocalMachine.CreateSubKey(ODBCINST_INI_REG_PATH + "ODBC Drivers");
if (driversKey == null) throw new Exception("ODBC Registry key for drivers does not exist");
var driverNames = driversKey.GetValueNames();
var ret = new List<string>();
foreach (var driverName in driverNames)
{
if (driverName != "(Default)")
{
ret.Add(driverName);
}
}
return ret.ToArray();
}
}
}
If I understand your question correctly you would like to bind a 'click' event to a button and run a method from your class Class. Just double click the button in the designer and you will be let to a just created function. In this function you can call the function from your class.
Please note!
Please never use Class as name of a class. It's a reserved word. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/
Related
I am creating a test app in VS C# to allow production to search for product information within a database and to create a csv file. The app currently works with 1 AccessDB which I added as a datasource in the IDE Data tab.
There is ~50 product databases and having to add each of these in the datasource tab is not viable especially if there are new databases created later on.
I was hoping something like the following would be possible to pass the databasePath as a parameter.
databasePath = txtBox.Text;
OleDbConnection conn1 = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=databasePath");
Is it possible to do this without having to go to the IDE and add a new datasource?
If all the databases have the exact same tables and columns and you are using TableAdapter try the following out.
Caveat It appears the following will not work when changing the connection string after the form has loaded. If you were to change it in a main form than open a child form the connection string works but not when changing the connection string in the same form as the TableAdapter. Best guess is connection string is set when the form initializes thus does not see the changed connection string.
Usage
var source = Path.Combine(ConnectionHelper.BasePath, "Database1.accdb");
ConnectionHelper.ChangeConnection(source,2);
Change BasePath to match your path to the databases.
using System.Configuration;
using System.Data.OleDb;
using System.IO;
using static System.Configuration.ConfigurationManager;
namespace AccessMultiConnections.Classes
{
public class ConnectionHelper
{
/// <summary>
/// Location of all databases
/// </summary>
public static string BasePath =
"C:\\Dotnetland\\Databases";
/// <summary>
/// Change database connection by index in connection string section
/// </summary>
/// <param name="source">Path and database name</param>
/// <param name="index">ordinal index of connection string</param>
/// <remarks>
/// Can change index parameter to a string name representing the connection
/// string if desire.
/// </remarks>
public static void ChangeConnection(string source, int index)
{
var config = OpenExeConfiguration(ConfigurationUserLevel.None);
var connectionStringsSection = (ConnectionStringsSection)config
.GetSection("connectionStrings");
// in this case the index to the connection string is 2
var current = connectionStringsSection.ConnectionStrings[index]
.ConnectionString;
var builder = new OleDbConnectionStringBuilder(current)
{
DataSource = source
};
connectionStringsSection.ConnectionStrings[index].ConnectionString =
builder.ConnectionString;
config.Save();
RefreshSection("connectionStrings");
Properties.Settings.Default.Reload();
}
/// <summary>
/// Provides the current database name without a path in appsettings.config
/// </summary>
/// <returns>File name for current connection</returns>
public static string CurrentConnectionString()
{
var config = OpenExeConfiguration(ConfigurationUserLevel.None);
var connectionStringsSection = (ConnectionStringsSection)config
.GetSection("connectionStrings");
var builder =
new OleDbConnectionStringBuilder(
connectionStringsSection.ConnectionStrings[2]
.ConnectionString);
return Path.GetFileName(builder.DataSource);
}
}
}
Edit - noticed I hard coded the connection string in CurrentConnectionString, here is the proper code.
/// <summary>
/// Provides the current database name without a path in appsettings.config
/// </summary>
/// <param name="index">ordinal index of connection string</param>
/// <returns>File name for current connection</returns>
public static string CurrentConnectionString(int index = 2)
{
var config = OpenExeConfiguration(ConfigurationUserLevel.None);
var connectionStringsSection = (ConnectionStringsSection)config
.GetSection("connectionStrings");
var builder =
new OleDbConnectionStringBuilder(
connectionStringsSection.ConnectionStrings[index]
.ConnectionString);
return Path.GetFileName(builder.DataSource);
}
I was trying to use C# loading Excel Name Manager, however, the name contains invalid chars like "\0". How to deal with that? I can't even 'var name = pkg.Workbook.Names;'.
Error Like this: System.ArgumentException:“Name \0 contains invalid characters”
using (ExcelPackage pkg = new ExcelPackage(filePatha))
{
var name = pkg.Workbook.Names;
foreach (ExcelNamedRange excelNamedRange in name)
{
Console.WriteLine($"IsNameHidden:{excelNamedRange.IsNameHidden}\n" +
$"Name:{excelNamedRange.Name}\n" +
$"Address:{excelNamedRange.Address}\n" +
$"Formula:{excelNamedRange.Formula}\n" +
$"Text:{excelNamedRange.Text}\n" +
$"Value:{excelNamedRange.Value}\n" +
$"FullAddressAbsolute:{excelNamedRange.FullAddressAbsolute}\n" +
$"---------------------------------\n");
}
Console.Read();
}
I read the EPPlus class
Seems that I can't use ExcelNamedRangeCollection if there are any invaild name in the Name Manager. I've already solved this with OpenXML.
/// <param name="Name">The name</param>
/// <param name="Range">The range</param>
/// <returns></returns>
public ExcelNamedRange Add(string Name, ExcelRangeBase Range)
{
if (!ExcelAddressUtil.IsValidName(Name))
throw new ArgumentException("Name contains invalid characters or is not valid.");
if (this._wb != Range._workbook)
throw new InvalidOperationException("The range must be in the same package. ");
return this.AddName(Name, Range);
}
I have written and rewritten the code several times now to delete records upon entering the 'name' in a textbox & clicking a button that will remove the row pertaining to the name entered. However, when I run the program and click on the button, the message box pops up stating that the particular records have been deleted, but when the table is displayed in the data grid view, the records are still present (they haven't been deleted from the table)
Please check my coding and tell me what's wrong in it, and how do I resolve it? thank you :)
Also I have to say, I've only learned basic stuff in C# programming..
Here is my coding:
private void btnremove_Click(object sender, EventArgs e)
{
con.Open();
DialogResult ans = MessageBox.Show("Are you sure you want to remove the selected records?",
"Confirmation For Membership Cancellation", MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (ans==DialogResult.Yes)
{
string sqldelete = "DELETE FROM membersdetails WHERE Name='" + txtname.Text + "'";
string deletesql = "DELETE FROM currentstatus WHERE Name_='" + txtname.Text + "'";
com = new SqlCommand(sqldelete,con);
com = new SqlCommand(deletesql, con);
SqlDataAdapter sqldataadapter = new SqlDataAdapter(com);
com.ExecuteNonQuery();
MessageBox.Show("Records have been removed- Membership has been cancelled",
"Membership Cancellation", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
con.Close();
com.Dispose();
}
There are some issues with your code, in its current state:
The block below, you are overwriting part of your deleting mechanism:
com = new SqlCommand(sqldelete,con);
com = new SqlCommand(deletesql, con);
SqlDataAdapter sqldataadapter = new SqlDataAdapter(com);
com.ExecuteNonQuery();
You should be able to merge the sqldelete and deletesql string variables into 1 command, whihc can be then passed on to the DB Engine.
From a coding perspective, classes should observe the Single Responsibility principle, that is, classes worry about doing only 1 thing. In your current code, the UI needs to look into keeping the UI updated and also keeping the DB updated. To fix this, we usually create repositories whose task is to wrap over the DB operations. This also allows you test individual layers of your application, and also giving you more flexibility should you decide to make any changes.
As is, your code is open for SQL Injection attacks, consider using prepared statements to cater for this problem.
Other than that, I cannot see anything inherently wrong with your deletion code. However, it might be the case that your grid is not being refreshed (while you are thinking that it is). The quickest and best way to check this is to use something like SQL Management Studio (or Azure Data) to connect to your DB and query the table directly.
The ExecuteNonQuery() has to be executed after and before of the last new instance:
com = new SqlCommand(sqldelete,con);
com.ExecuteNonQuery();
com = new SqlCommand(deletesql, con);
com.ExecuteNonQuery();
// Then, you can show the table
string sqlSelect = "SELECT * FROM yourtable WHERE bla bla...";
SqlDataAdapter sqldataadapter = new SqlDataAdapter(sqlSelect, con);
DataTable tbl = new DataTable();
sqldataadapter.Fill(tbl);
yourDataGridView.DataSource = tbl;
Try this, I hope it could help.
The following is an example which may need some changes to fit into your code. The idea is to setup a DataGridView data source with a BindingSource that has required columns from a table for member details. Data operations are in a class to separate frontend operations from data operations. Note there is no need for a SqlDataAdapter which is overkill.
For the delete operation best to use a primary key as this is constant while a name may change at some point.
The following class is a mock up for read and delete operations, make sure you understand it's not a copy-n-paste solution so take time to read the code before trying it out.
Data class
using System.Collections.Generic;
using System.Data.SqlClient;
namespace YourNamespace
{
public class MembershipOperations
{
/// <summary>
/// Connection string to your database
/// - change TODO to your environment
/// - Integrated Security (check this too)
/// </summary>
private static readonly string _connectionString =
"Data Source=****TODO****;" +
"Initial Catalog=****TODO****;" +
"Integrated Security=True";
/// <summary>
/// Delete a single member by primary key
/// Recommend adding a try-catch to the open and ExecuteNonQuery lines
/// </summary>
/// <param name="memberIdentifier">Member primary key</param>
/// <returns>
/// Success of the operation
/// </returns>
public static bool RemoveSingleOrder(int memberIdentifier)
{
bool success = false;
string deleteStatement = "DELETE FROM membersdetails WHERE id = #id";
using (var cn = new SqlConnection { ConnectionString = _connectionString })
{
using (var cmd = new SqlCommand { Connection = cn, CommandText = deleteStatement })
{
cmd.Parameters.AddWithValue("id", memberIdentifier);
cn.Open();
success = cmd.ExecuteNonQuery() == 1;
}
}
return success;
}
/// <summary>
/// Provides a list which in the frontend a user can select
/// a member such as a ComboBox or ListBox etc.
///
/// When they select a user you have code to cast the selected item
/// to a MemberDetails instance and pass the primary key to the RemoveSingleOrder
/// method.
///
/// Note in the frontend if there are many members consider providing a incremental
/// search feature to the frontend control displaying member details.
/// </summary>
/// <returns></returns>
public static List<MemberDetails> GetMemberDetails()
{
var memberDetails = new List<MemberDetails>();
/*
* - Create a connection and command object with
* SELECT column names including 'id' primary key
*
* - Use a DataReader to loop through records into
* memberDetails variable
*/
return memberDetails;
}
}
/// <summary>
/// Place in a class file named MemberDetails.cs
/// </summary>
public class MemberDetails
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// more properties
/// <summary>
/// Optional, can assist with debugging and/or
/// for display purposes
/// </summary>
/// <returns></returns>
public override string ToString() => $"{FirstName} {LastName}";
}
}
Helper method for asking a question with the default button is No, not Yes.
using System.Windows.Forms;
namespace YourNamespace
{
public static class Dialogs
{
public static bool Question(string text)
{
return (MessageBox.Show(
text,
Application.ProductName,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2) == DialogResult.Yes);
}
}
}
Form code
using System;
using System.Windows.Forms;
namespace YourNamespace
{
public partial class Form1 : Form
{
private readonly BindingSource _membersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
Shown += Form1_Shown;
}
/// <summary>
/// Setup columns in the IDE for MembersDataGridView that
/// exclude the primary key
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Shown(object sender, EventArgs e)
{
MembersDataGridView.AutoGenerateColumns = false;
_membersBindingSource.DataSource = MembershipOperations.GetMemberDetails();
MembersDataGridView.DataSource = _membersBindingSource;
}
private void RemoveMemberButton_Click(object sender, EventArgs e)
{
if (_membersBindingSource.Current == null) return;
var member = (MemberDetails)_membersBindingSource.Current;
if (!Dialogs.Question($"Remove {member}")) return;
if (!MembershipOperations.RemoveSingleOrder(member.Id))
{
MessageBox.Show($"Failed to remove {member}");
}
}
}
}
I have a simple and basic question: how do I make my app save changes on the textbox and other editable tools (like radiobuttons/colors etc)?
I am coding a UWP app on Visual Studio.
When I lunch the app on VS, the text I write in the textboxes disapear when I close the app.
Sorry I just started a few days ago and can't find a solution...
Thanks!
you need to store that data locally, when you closing your app. so when you restart app first fetch data from that local storage and save or append it in your textbox.
You can use below two ways to store it.
Create one text file and store your data in it, so you can fetch data whenever your app
is restarted.
you can use settings for store local data. please check below link for more information.
https://learn.microsoft.com/en-us/windows/uwp/get-started/settings-learning-track
By localSettings, You can store your data locally in your machine.
public static class LocalSettingsHelper
{
private static ApplicationDataContainer _localSettings = ApplicationData.Current.LocalSettings;
/// <summary>
/// Create Local Settings storage Container
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="container">Container</param>
/// <param name="containerValue">ContainerValue</param>
/// <param name="value">Value</param>
internal static void SetContainer<T>(string container, string containerValue, T value)
{
var containerName = _localSettings.CreateContainer(container, ApplicationDataCreateDisposition.Always);
_localSettings.Containers[container].Values[containerValue] = value != null ? JsonConvert.SerializeObject(value) : null;
}
/// <summary>
/// Get Local Settings Container
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="container">Container</param>
/// <param name="containerValue">ContainerValue</param>
/// <returns>Value as Type</returns>
internal static T GetContainerValue<T>(string container, string containerValue)
{
var containerName = _localSettings.CreateContainer(container, ApplicationDataCreateDisposition.Always);
string currentValue = _localSettings.Containers[container].Values[containerValue] as string;
if (currentValue == null)
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(currentValue);
}
}
I'm looking for a way to programmatically get the summary portion of Xml-comments of a method in ASP.net.
I have looked at the previous related posts and they do not supply a way of doing so in a web environment.
I can not use any 3rd party apps and due to a web environment, Visual studio plugin's aren't much use either.
The closest thing I have found to a working solution was the JimBlackler project, but it only works on DLL's.
Naturally, something like 'supply .CS file, get XML documentation' would be optimal.
Current situation
I have a web-service and trying to dynamically generate documentation for it.
Reading the Methods, and properties is easy, but getting the Summary for each method is throwing me off a bit.
/// <summary>
/// This Is what I'm trying to read
/// </summary>
public class SomeClass()
{
/// <summary>
/// This Is what I'm trying to read
/// </summary>
public void SomeMethod()
{
}
}
A Workaround - Using reflection on Program.DLL/EXE together with Program.XML file
If you take a look at the sibling .XML file generated by Visual Studio you will see that there is a fairly flat hierarchy of /members/member.
All you have to do is get hold on each method from your DLL via MethodInfo object. Once you have this object you turn to the XML and use XPATH to get the member containing the XML documentation for this method.
Members are preceded by a letter. XML doc for methods are preceded by "M:" for class by "T:" etc.
Load your sibling XML
string docuPath = dllPath.Substring(0, dllPath.LastIndexOf(".")) + ".XML";
if (File.Exists(docuPath))
{
_docuDoc = new XmlDocument();
_docuDoc.Load(docuPath);
}
Use this xpath to get the member representing the method XML docu
string path = "M:" + mi.DeclaringType.FullName + "." + mi.Name;
XmlNode xmlDocuOfMethod = _docuDoc.SelectSingleNode(
"//member[starts-with(#name, '" + path + "')]");
Now scan childnodes for all the rows of "///"
Sometimes the /// Summary contains extra blanks, if this bothers use this to remove
var cleanStr = Regex.Replace(row.InnerXml, #"\s+", " ");
The XML summary isn't stored in the .NET assembly - it's optionally written out to an XML file as part of your build (assuming you're using Visual Studio).
Consequently there is no way to "pull out" the XML summaries of each method via reflection on a compiled .NET assembly (either .EXE or .DLL) - because the data simply isn't there for you to pull out. If you want the data, you'll have to instruct your build environment to output the XML files as part of your build process and parse those XML files at runtime to get at the summary information.
You could 'document' your method using the System.ComponentModel.DataAnnotations.DisplayAttribute attribute, e.g.
[Display(Name = "Foo", Description = "Blah")]
void Foo()
{
}
then use reflection to pull the description at runtime.
A deleted post, made by #OleksandrIeremenko, on this thread links to this article https://jimblackler.net/blog/?p=49 which was the basis for my solution.
Below is a modification of Jim Blackler's code making extension methods off the MemberInfo and Type objects and adding code that returns the summary text or an empty string if not available.
Usage
var typeSummary = typeof([Type Name]).GetSummary();
var methodSummary = typeof([Type Name]).GetMethod("[Method Name]").GetSummary();
Extension Class
/// <summary>
/// Utility class to provide documentation for various types where available with the assembly
/// </summary>
public static class DocumentationExtensions
{
/// <summary>
/// Provides the documentation comments for a specific method
/// </summary>
/// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param>
/// <returns>The XML fragment describing the method</returns>
public static XmlElement GetDocumentation(this MethodInfo methodInfo)
{
// Calculate the parameter string as this is in the member name in the XML
var parametersString = "";
foreach (var parameterInfo in methodInfo.GetParameters())
{
if (parametersString.Length > 0)
{
parametersString += ",";
}
parametersString += parameterInfo.ParameterType.FullName;
}
//AL: 15.04.2008 ==> BUG-FIX remove “()” if parametersString is empty
if (parametersString.Length > 0)
return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")");
else
return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name);
}
/// <summary>
/// Provides the documentation comments for a specific member
/// </summary>
/// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param>
/// <returns>The XML fragment describing the member</returns>
public static XmlElement GetDocumentation(this MemberInfo memberInfo)
{
// First character [0] of member type is prefix character in the name in the XML
return XmlFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name);
}
/// <summary>
/// Returns the Xml documenation summary comment for this member
/// </summary>
/// <param name="memberInfo"></param>
/// <returns></returns>
public static string GetSummary(this MemberInfo memberInfo)
{
var element = memberInfo.GetDocumentation();
var summaryElm = element?.SelectSingleNode("summary");
if (summaryElm == null) return "";
return summaryElm.InnerText.Trim();
}
/// <summary>
/// Provides the documentation comments for a specific type
/// </summary>
/// <param name="type">Type to find the documentation for</param>
/// <returns>The XML fragment that describes the type</returns>
public static XmlElement GetDocumentation(this Type type)
{
// Prefix in type names is T
return XmlFromName(type, 'T', "");
}
/// <summary>
/// Gets the summary portion of a type's documenation or returns an empty string if not available
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetSummary(this Type type)
{
var element = type.GetDocumentation();
var summaryElm = element?.SelectSingleNode("summary");
if (summaryElm == null) return "";
return summaryElm.InnerText.Trim();
}
/// <summary>
/// Obtains the XML Element that describes a reflection element by searching the
/// members for a member that has a name that describes the element.
/// </summary>
/// <param name="type">The type or parent type, used to fetch the assembly</param>
/// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param>
/// <param name="name">Where relevant, the full name qualifier for the element</param>
/// <returns>The member that has a name that describes the specified reflection element</returns>
private static XmlElement XmlFromName(this Type type, char prefix, string name)
{
string fullName;
if (string.IsNullOrEmpty(name))
fullName = prefix + ":" + type.FullName;
else
fullName = prefix + ":" + type.FullName + "." + name;
var xmlDocument = XmlFromAssembly(type.Assembly);
var matchedElement = xmlDocument["doc"]["members"].SelectSingleNode("member[#name='" + fullName + "']") as XmlElement;
return matchedElement;
}
/// <summary>
/// A cache used to remember Xml documentation for assemblies
/// </summary>
private static readonly Dictionary<Assembly, XmlDocument> Cache = new Dictionary<Assembly, XmlDocument>();
/// <summary>
/// A cache used to store failure exceptions for assembly lookups
/// </summary>
private static readonly Dictionary<Assembly, Exception> FailCache = new Dictionary<Assembly, Exception>();
/// <summary>
/// Obtains the documentation file for the specified assembly
/// </summary>
/// <param name="assembly">The assembly to find the XML document for</param>
/// <returns>The XML document</returns>
/// <remarks>This version uses a cache to preserve the assemblies, so that
/// the XML file is not loaded and parsed on every single lookup</remarks>
public static XmlDocument XmlFromAssembly(this Assembly assembly)
{
if (FailCache.ContainsKey(assembly))
{
throw FailCache[assembly];
}
try
{
if (!Cache.ContainsKey(assembly))
{
// load the docuemnt into the cache
Cache[assembly] = XmlFromAssemblyNonCached(assembly);
}
return Cache[assembly];
}
catch (Exception exception)
{
FailCache[assembly] = exception;
throw;
}
}
/// <summary>
/// Loads and parses the documentation file for the specified assembly
/// </summary>
/// <param name="assembly">The assembly to find the XML document for</param>
/// <returns>The XML document</returns>
private static XmlDocument XmlFromAssemblyNonCached(Assembly assembly)
{
var assemblyFilename = assembly.Location;
if (!string.IsNullOrEmpty(assemblyFilename))
{
StreamReader streamReader;
try
{
streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename, ".xml"));
}
catch (FileNotFoundException exception)
{
throw new Exception("XML documentation not present (make sure it is turned on in project properties when building)", exception);
}
var xmlDocument = new XmlDocument();
xmlDocument.Load(streamReader);
return xmlDocument;
}
else
{
throw new Exception("Could not ascertain assembly filename", null);
}
}
}
You can use Namotion.Reflection NuGet package to get these information:
string summary = typeof(Foo).GetXmlDocsSummary();
You can look at https://github.com/NSwag/NSwag - source for nuget NSwag.CodeGeneration - it gets summary as well, usage
var generator = new WebApiAssemblyToSwaggerGenerator(settings);<br/>
var swaggerService = generator.GenerateForController("namespace.someController");<br/>
// string with comments <br/>
var swaggerJson = swaggerService.ToJson();
(try ILSPY decompiler against your dll, you check code and comments)
If you have access to the source code you're trying to get comments for, then you can use Roslyn compiler platform to do that. It basically gives you access to all the intermediary compiler metadata and you can do anything you want with it.
It's a bit more complicated than what other people are suggesting, but depending on what your needs are, might be an option.
It looks like this post has a code sample for something similar.