65.9K
CodeProject 正在变化。 阅读更多。
Home

模型驱动开发和原型设计(云部分 3)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2014年6月15日

CPOL

13分钟阅读

viewsIcon

17942

代码生成如何使分布式架构成为一项轻松的任务。

引言

这是我系列文章中关于代码生成的第三篇,将讨论将业务应用程序迁移到云(IaaS)。第一篇文章介绍了UML和模型转换的使用,以提高开发速度。第二篇文章讨论了XSLT提供的可扩展性或增强功能。

背景

今天迁移到云的原因很明确。您可以快速部署系统,而无需等待硬件的运输和设置。主要驱动因素是成本节省和响应需求的敏捷性。

架构

在开始编码之前,需要进行规划和一些研究。在考虑分布式环境时,会遇到一些问题。它们如下:

  • 安全性 - 至关重要,应尽早解决。
  • 代理身份验证 - 凭证存储的单一来源。
  • 分布式架构范围 - 将内部客户端/服务器应用程序迁移到云。

首先,应考虑安全性。将应用程序从内部范围(内网)迁移到公共(互联网)范围需要对现有架构进行一些分析。

下图描绘了现有的客户端/服务器架构。

在前两篇文章中创建的原型应用程序使用了一个数据库后端(SQLite),该数据库后端有效地运行在同一台机器上,但可以假设这里有一个典型的远程数据库服务器。在这种情况下,安全性由数据库负责,因此需要数据库登录。在应用程序中实现的登录机制根本不是真正的安全,因为数据库凭据存储在客户端。客户端可以直接访问数据库,因此可能成为被访问的入口点。另一方面,为每个用户使用数据库级别的登录涉及许可问题,可能并不便宜,因此选择了应用程序级别的登录。

当考虑互联网场景时,由于您无法真正控制哪个客户端登录到数据库,因此客户端的信任可能会丢失。如果客户端负责处理每个用户的身份验证,客户端可能会省略此步骤,直接连接到数据库并允许用户执行任何操作。客户端不再可信。

在考虑到服务器至少必须承担身份验证角色的情况下,是时候三思了。如果应用程序需要与另一个后端通信怎么办?这时一个模式就发挥作用了:代理身份验证。

完成基本设计将显示一些重大变化。

假定客户端位于互联网上的任何位置,因此不再信任其直接连接到数据库后端。必须在数据库和客户端之间放置一个服务器。现在是时候介绍一下XPO了,这是一个由http://www.devexpress.com开发的商业ORM。他们提供了使生活更轻松的类。以前直接连接到数据库(在我的例子中是SQLite)的客户端连接字符串,现在已更改为连接到数据存储服务。这是一个支持WCF作为通信机制的客户端服务器库。这使得拆分客户端和服务器相关组件的代码变得容易。更改连接字符串在第一步似乎很容易集成到以前的客户端中。

到目前为止,我有了第一个分布式场景,其中客户端连接到一个WCF服务,该服务通过IDataStore接口(XPO)提供数据库后端。我能够使用wsHttpBinding并使通信安全。在互联网或公共场景中,这是另一个要点。数据在不受信任的网络上传输时必须加密。

现在应用程序能够安全地通过互联网进行通信,但是 - 它仍然具有相同的身份验证机制 - 在客户端。客户端包含一些内部表,这些表已添加到生成的代码中,以通过简单的登录屏幕启用身份验证。将其移动到同一个WCF服务并没有真正使服务安全。只要使用兼容客户端中的IDataService接口,任何人都可以访问此服务。客户端仍然通过查询我添加到生成代码中的内部表来验证自身。

第二张图中描绘的互联网场景显示了身份验证和应用程序关注点分离的下一步。添加了一个独立的WCF服务,使用与应用程序当前使用的WCF服务相同的技术。这个新服务使用WIF实现了代理身份验证模式。客户端必须在访问应用程序服务之前联系此服务以获取令牌。从现在开始,WCF应用程序服务仅接受由此代理身份验证服务发出的令牌。WIF(安全令牌服务)由客户端联系以获取令牌,并提供用户在登录屏幕上提供的登录凭据。

这是模板中的下一个更改,不再查询内部用户表以查找具有该密码的用户。相反,凭据存储在SecureString(.NET)中,稍后在IDataStore客户端中用作登录凭据。WCF客户端在内部联系STS以获取令牌,如果正确,它就可以联系所需的WCF服务,并出示令牌进行身份验证。

在基本场景设置(代码更改和WCF配置以针对STS进行身份验证)之后,我能够进行第一次测试部署。此时,服务实现为Windows服务,我使用InstallUtil手动安装了它们。

编写纯代码原型部分

正如您在上一章中所读到的,代码几乎已准备好进行模板化。所有组件都已准备就绪,可以部署了,是时候更改代码生成器模板中我手动修改原型代码的点了。需要进行清理,以及一些额外的东西,例如服务的MSI安装程序。完成这些操作后,我的解决方案中的Visual Studio项目数量增加了。我知道我需要对现有的代码生成器模板进行多项更改,并为全新的项目(STS WCF服务,应用程序WCF服务,使用WIX的相应安装程序项目)添加新内容。

模板化部分

在这里,我想介绍第一个代码片段,它在更改现有模板方面给了我很大的帮助。

        <Macro name="Templatize" Ctrl="yes" Alt="no" Shift="yes" Key="84">
            <Action type="3" message="1700" wParam="0" lParam="0" sParam="" />
            <Action type="3" message="1601" wParam="0" lParam="0" sParam="&" />
            <Action type="3" message="1625" wParam="0" lParam="0" sParam="" />
            <Action type="3" message="1602" wParam="0" lParam="0" sParam="&amp;" />
            <Action type="3" message="1702" wParam="0" lParam="768" sParam="" />
            <Action type="3" message="1701" wParam="0" lParam="1609" sParam="" />
            <Action type="3" message="1700" wParam="0" lParam="0" sParam="" />
            <Action type="3" message="1601" wParam="0" lParam="0" sParam="&lt;" />
            <Action type="3" message="1625" wParam="0" lParam="0" sParam="" />
            <Action type="3" message="1602" wParam="0" lParam="0" sParam="&lt;" />
            <Action type="3" message="1702" wParam="0" lParam="768" sParam="" />
            <Action type="3" message="1701" wParam="0" lParam="1609" sParam="" />
            <Action type="3" message="1700" wParam="0" lParam="0" sParam="" />
            <Action type="3" message="1601" wParam="0" lParam="0" sParam="&gt;" />
            <Action type="3" message="1625" wParam="0" lParam="0" sParam="" />
            <Action type="3" message="1602" wParam="0" lParam="0" sParam="&gt;" />
            <Action type="3" message="1702" wParam="0" lParam="768" sParam="" />
            <Action type="3" message="1701" wParam="0" lParam="1609" sParam="" />
        </Macro>

此片段是Notepad++宏,用于帮助将粘贴的代码格式化为XSLT兼容的代码。它将特殊字符替换为特殊表示形式,以免干扰用于格式化XML文档的字符。快捷方式是Ctrl+T表示“模板化” :-)

仅在纯代码内容中使用此宏,不要在XSLT模板中使用。如果您这样做,您还会替换XSLT模板中的XML标记。我遵循的做法是创建一个新文档,将我的代码粘贴在那里,按Ctrl+T Ctrl+A Ctrl+C,然后就可以将其插入模板了。这非常快,而且不会漏掉任何损坏模板的字符。

将模板包含在现有模板中

使用上面Notepad++中的宏,我能够非常快速地在各种地方更改模板代码,这些代码与先前模板版本生成的原型代码相关。

例如,这是模板中的一个先前代码片段,需要进行更改。

       protected static bool _DALInitialized = false;
        public static void InitDAL()
        {
            if (_DALInitialized)
                return;
            
            _DALInitialized = true;
                
            try
            {
<xsl:if test="$USE_XPO='yes'">
                DevExpress.Xpo.XpoDefault.DataLayer = DevExpress.Xpo.XpoDefault.GetDataLayer(
<xsl:if test="$DB_ENGINE='postgresql'">
                DevExpress.Xpo.DB.PostgreSqlConnectionProvider.GetConnectionString("vmhost.behrens.de", "dba", "trainres", "<xsl:value-of select="$ApplicationName"/>DevExpress"),
</xsl:if>
<xsl:if test="$DB_ENGINE='access'">
                DevExpress.Xpo.DB.AccessConnectionProvider.GetConnectionString(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @".\<xsl:value-of select="$ApplicationName"/>.mdb")),
</xsl:if>
<xsl:if test="$DB_ENGINE='sqlite'">
<xsl:if test="$ApplicationName='lbDMFManager'">
                DevExpress.Xpo.DB.SQLiteConnectionProvider.GetConnectionString(Path.Combine(@"C:\lbDMF", @"lbDMF.db3")),
</xsl:if>
<xsl:if test="$ApplicationName!='lbDMFManager'">
                DevExpress.Xpo.DB.SQLiteConnectionProvider.GetConnectionString(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @".\<xsl:value-of select="$ApplicationName"/>.db3")),
</xsl:if>
</xsl:if>
                DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
</xsl:if>
            }
            catch (Exception e)
            {
                XtraMessageBox.Show("Something went wrong while connecting to the database '<xsl:value-of select="$ApplicationName"/>DevExpress': " + e.Message, "Error");
            }
        }

该函数负责连接到数据库后端。由于我使用了XPO ORM,因此我能够以隔离的方式更改代码。DevExpress的开发人员做得很好,通过仅更改一些与连接字符串相关的代码,就可以实现DAL的WCF化。更改的结果是查询一个标志以启用远程通信。以下代码包含粗体标记的文本,以突出模板的一些重要可配置性。这些参数被传递到模板中,以便轻松选择功能,因为我也希望能够切换回仅使用本地场景。(切换回功能尚未测试,但已计划)

        protected static bool _DALInitialized = false;
        
        protected static Binding CreateBinding(string security_mode)
        {
            Binding _binding;

            switch (security_mode)
            {
<xsl:if test="$SUPPORT_BROKERED_AUTH='yes'">
                case "Brokered_Auth":
                    CustomBinding bindingBA = new CustomBinding("UsernameBinding"); // Use the configuration
                    _binding = bindingBA;
                    break;
</xsl:if>                    
                case "Transport":
                    NetTcpBinding bindingT = new NetTcpBinding(SecurityMode.Transport);
                    bindingT.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
                    bindingT.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;

                    bindingT.MaxReceivedMessageSize = Int32.MaxValue;
                    bindingT.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
                    bindingT.ReaderQuotas.MaxDepth = Int32.MaxValue;
                    bindingT.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
                    bindingT.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
                    _binding = bindingT;

                    break;
                case "Message":
                    NetTcpBinding bindingM = new NetTcpBinding(SecurityMode.Message);
                    bindingM.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
                    bindingM.Security.Message.ClientCredentialType = MessageCredentialType.None;
                    _binding = bindingM;
                    break;
                case "None":
                    NetTcpBinding bindingN = new NetTcpBinding(SecurityMode.None);
                    _binding = bindingN;

                    break;
                default:
                    NetTcpBinding bindingD = new NetTcpBinding();
                    _binding = bindingD;
                    break;
            }
            
            return _binding;
        }

        protected static X509CertificateValidationMode SetCertificateValidationMode(string X509CertificateValidationMode)
        {
            X509CertificateValidationMode mode = System.ServiceModel.Security.X509CertificateValidationMode.ChainTrust;

            switch (X509CertificateValidationMode)
            {
                case "None":
                    mode = System.ServiceModel.Security.X509CertificateValidationMode.None;
                    break;
                case "PeerTrust":
                    mode = System.ServiceModel.Security.X509CertificateValidationMode.PeerTrust;
                    break;
                case "PeerOrChainTrust":
                    mode = System.ServiceModel.Security.X509CertificateValidationMode.PeerOrChainTrust;
                    break;
                case "ChainTrust":
                    mode = System.ServiceModel.Security.X509CertificateValidationMode.ChainTrust;
                    break;
            }

            return mode;
        }

        protected string convertToUNSecureString(SecureString secstrPassword)
        {
            IntPtr unmanagedString = IntPtr.Zero;
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secstrPassword);
                return Marshal.PtrToStringUni(unmanagedString);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
            }
        }

        public bool InitDAL(string user, SecureString pass)
        {
            if (_DALInitialized)
                return true;
                            
            try
            {
<xsl:if test="$USE_DATASTORECLIENT='yes'">
                string host = ConfigurationManager.AppSettings.Get("DataStoreHost");
                string port = ConfigurationManager.AppSettings.Get("DataStorePort");
                string security_mode = ConfigurationManager.AppSettings.Get("DataStoreSecurityMode");
                string X509CertificateValidationMode = ConfigurationManager.AppSettings.Get("X509CertificateValidationMode");
                string LocalMachine_My_BySubject_ClientCertificate = ConfigurationManager.AppSettings.Get("LocalMachine_My_BySubject_ClientCertificate");
                string LocalMachine_My_BySubject_ServerCertificate = ConfigurationManager.AppSettings.Get("LocalMachine_My_BySubject_ServerCertificate");
                string DnsIdentity = ConfigurationManager.AppSettings.Get("DnsIdentity");

                string auxPath = "net.tcp://" + host + ":" + port + "/<xsl:value-of select="$ApplicationName"/>Service";

                // Security mode tells also when to use brokered auth, if supported.
                
                Binding binding = CreateBinding(security_mode);

                EndpointAddress addr;

                if (DnsIdentity != null && DnsIdentity != "")
                {
                    addr = new EndpointAddress(new Uri(auxPath), EndpointIdentity.CreateDnsIdentity(DnsIdentity));
                }
                else
                {
                    addr = new EndpointAddress(new Uri(auxPath));
                }

                DevExpress.Xpo.DB.DataStoreClient dataLayer;

                // Auth with STS
                if (security_mode == "Brokered_Auth")
                {
                    dataLayer = new DevExpress.Xpo.DB.DataStoreClient("<xsl:value-of select="$ApplicationName"/>Endpoint");
                }
                else
                {
                    dataLayer = new DevExpress.Xpo.DB.DataStoreClient(binding, addr);
                }

                string password = convertToUNSecureString(pass);

                InMemoryIssuedTokenCache cache = new InMemoryIssuedTokenCache();

                dataLayer.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = SetCertificateValidationMode(X509CertificateValidationMode);
                
<xsl:if test="$SUPPORT_BROKERED_AUTH='yes'">
                // Auth with STS
                if (security_mode == "Brokered_Auth") {
                    
                    DurableIssuedTokenClientCredentials durableCreds = new DurableIssuedTokenClientCredentials();
                    durableCreds.IssuedTokenCache = cache;

                    dataLayer.ChannelFactory.Endpoint.Behaviors.Remove&lt;ClientCredentials&gt;();
                    dataLayer.ChannelFactory.Endpoint.Behaviors.Add(durableCreds);

                    dataLayer.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = SetCertificateValidationMode(X509CertificateValidationMode);
                    if (LocalMachine_My_BySubject_ServerCertificate != null && LocalMachine_My_BySubject_ServerCertificate != "")
                        dataLayer.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, LocalMachine_My_BySubject_ServerCertificate); //"cloud.lollisoft.de");
                    if (LocalMachine_My_BySubject_ClientCertificate != null && LocalMachine_My_BySubject_ClientCertificate != "")
                        dataLayer.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, LocalMachine_My_BySubject_ClientCertificate); //"cloud.lollisoft.de");

                    dataLayer.ChannelFactory.Credentials.UserName.UserName = user;
                    dataLayer.ChannelFactory.Credentials.UserName.Password = password;
                }
</xsl:if>                    

                DevExpress.Xpo.SimpleDataLayer dtLayer = new DevExpress.Xpo.SimpleDataLayer(dataLayer);
                DevExpress.Xpo.XpoDefault.DataLayer = dtLayer;
                
                if (security_mode == "Brokered_Auth")
                {
                    try
                    {
                        // Make a server roundtrip to test login credentials are working.
                        // This enables a retry without using specific select statements.
                        DevExpress.Xpo.UnitOfWork uow = new DevExpress.Xpo.UnitOfWork();

                        uow.ExplicitBeginTransaction();
                        uow.ExplicitRollbackTransaction();

                        password = "";
                    }
                    catch (MessageSecurityException secex)
                    {
                        if (secex.InnerException != null)
                        {
                            XtraMessageBox.Show("Something went wrong while connecting to the database '<xsl:value-of select="$ApplicationName"/>': " + secex.InnerException.Message, "Error");
                            return false;
                        }
                    }
                }
</xsl:if>            
<xsl:if test="USE_DATASTORECLIENT!='yes'">
            
<xsl:if test="$USE_XPO='yes'">
                DevExpress.Xpo.XpoDefault.DataLayer = DevExpress.Xpo.XpoDefault.GetDataLayer(
<xsl:if test="$DB_ENGINE='postgresql'">
                DevExpress.Xpo.DB.PostgreSqlConnectionProvider.GetConnectionString("vmhost.behrens.de", "dba", "trainres", "<xsl:value-of select="$ApplicationName"/>"),
</xsl:if>
<xsl:if test="$DB_ENGINE='access'">
                DevExpress.Xpo.DB.AccessConnectionProvider.GetConnectionString(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @".\<xsl:value-of select="$ApplicationName"/>.mdb")),
</xsl:if>
<xsl:if test="$DB_ENGINE='sqlite'">
<xsl:if test="$ApplicationName='lbDMFManager'">
                DevExpress.Xpo.DB.SQLiteConnectionProvider.GetConnectionString(Path.Combine(@"C:\lbDMF", @"lbDMF.db3")),
</xsl:if>
<xsl:if test="$ApplicationName!='lbDMFManager'">
                DevExpress.Xpo.DB.SQLiteConnectionProvider.GetConnectionString(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @".\<xsl:value-of select="$ApplicationName"/>.db3")),
</xsl:if>
</xsl:if>
                DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
</xsl:if>

</xsl:if>            
            }
            catch (Exception e)
            {
                XtraMessageBox.Show("Something went wrong while connecting to the database '<xsl:value-of select="$ApplicationName"/>': " + e.Message, "Error");
                return false;
            }
            
            _DALInitialized = true;
            return true;
        }

模板中的其他部分以相同的方式进行了更改,通过检查原型代码中的更改,使其能够在互联网场景中运行。在这里,我建议使用版本管理系统来存储代码版本,以方便比较。

项目中还有许多新文件,需要对整个模板进行大修,将新的XSLT文件集成到生成过程中。我不想把所有这些都放到文章里,因为它太多了:-)

参数化WCF端点和证书

读者需要关注的重要内容是服务运行的配置。您通常无法访问我的云环境,因此需要对模板进行一些更改,并在应用程序模型中进行一些更改。模板中的更改更可能是静态的,并且与用于保护通信通道的系统中的证书相关。您需要提供自己的证书。

在模板中更改证书指纹是您的一个练习:-)

另一部分是应用程序配置,它存储为UML参数,位于包含实体类的UML包中。实际上是最里面的包。需要以下参数:

  • 应用程序产品的MSI升级代码,本质上是一个UUID,您可以在Visual Studio中生成。
  • 应用程序产品的MSI产品ID。两者都是WCF应用程序服务所必需的,而不是客户端。客户端仍然没有安装程序。
  • MSI还需要一个版本。
  • 数据存储后端WCF地址。这是提供应用程序数据的服务。
  • STS后端WCF地址。客户端将联系此服务以获取令牌。

在此处重复配置名称和值以供参考:

UpgradeCode = 48dc066c-<snip>

ProductCode = 0BB62EC4-<snip>

ProductVersion = 1.0.4.0

DataStoreBaseAddress = net.tcp://cloud.yourserver.de:<port>

StsBaseAddress = net.tcp://cloud.yourserver.de:<port>

修改STS模板服务

在此我需要解释一下如何创建STS后端服务。它基于Pablo M. Cibraro的一篇文章:http://weblogs.asp.net/cibrax/federation-over-tcp-with-wcf。他提供的示例代码是一个简单的控制台演示,关于使用NetTcpBinding的联合场景。通过检查代码并进行多次试验,我能够得到我的版本,使用Windows服务托管WCF STS服务。这是我对此组件进行模板化的基础。

注意:使用net tcp绑定场景显示了一些额外的我需要理解的工作和复杂性,但为了基于Pablo的工作实现我的版本,这是值得的。它只是更低的负载开销,从而减少了流量并提高了速度。但请注意,这仅是.NET代码的绑定。我正在使用.NET作为客户端,这有助于决定使用它。

在我将STS服务运行起来,包括POC(概念验证)基础代码后,我需要使其成为一个真正可用的组件。该模板是用基本的POC场景创建的,只需要进行修改。所需代码从我在客户端的模板中的原始位置复制过来,并放置在模板中,以便从STS逻辑中访问。本质上,实体类“SecurityUsers”被复制,模板也相应地进行了修改。下一步是实现一个真正的身份验证机制。我替换了由模拟用户帐户组成的代码,并用访问和过滤登录尝试(带有密码)的逻辑替换了它。这是一个WCF配置可以钩入正确位置以激活此机制的类。还有更多的代码需要更改,但模式也需要增强,以更改列出的硬编码服务(在应用程序配置中的受众URI配置)和SAML创建逻辑。是时候创建一个新的UML模型了。

为STS后端模式创建UML模型

为了快速完成,我复制了客户端身份验证机制的实体类,并更改了相关代码,以便从数据库而不是应用程序配置中获取配置值。我有一个登录功能,其中我利用了STS服务中的SecurityUsers表。

然后,我创建了一个UML模型,将用于配置尚未硬编码的其余部分。所需表的数据库模式如下:

UML模型用于两个问题。一个是STS服务,另一个是它的管理应用程序。首先生成管理应用程序,我能够获取更多代码!

在为新的管理客户端生成代码后,我获取了剩余的实体类并将其放入STS服务中。完成这些后,我替换了相关代码,以便根据数据库中的配置生成SAML令牌。列出的受众URI的创建也通过配置表完成。

即使STS服务只需编写一次,我也决定将其放入模板中。这是因为如果我需要更多表来构建STS功能,大部分代码都可以生成。我认为管理模板比管理一个横切库(包括一个产品(STS服务+管理))的工作量差不多。这一点可以作为对我的决定的反驳。

结论

使用代码生成技术可以提高开发速度。它帮助我开发了通常需要手动编写的部分代码。即使STS服务原型是手动编写的(通过样本),后来也通过生成的代码进行了增强。因此,在面向服务的环境中,确实可以使用模型驱动开发方法。使用100%生成方法,您可能会遇到一些对不需要生成的代码的抱怨,但在原型开发中,它可以帮助您快速创建独立的解决方案。

使用代码

本文的代码基于我的软件发布,因此可以在此处列出的地方下载。

http://lbdmf.sourceforge.net/

下载包如下:

原型应用程序:http://sourceforge.net/projects/lbdmf/files/lbdmf/lbDMF-1.0.4.2-final/lbDMF-BinSamples-1.0.4.2-final.exe/download

代码生成器模板: http://sourceforge.net/projects/lbdmf/files/lbdmf/lbDMF-1.0.4.4-final/lbDMF-CAB-DevExpress-Generator-Compilation-1.0.4.4-final.exe/download

如何使用BoUML或ArgoUML创建UML模型的文档可在此处下载:

http://sourceforge.net/projects/lbdmf/files/lbdmf/lbDMF-1.0.4.2-final/ApplicationprototypingDokumentation.pdf/download (稍后更新)

原型应用程序包含STSManagement应用程序的UML模型以及可以导入ArgoUML的XMI模型。这样,您就可以生成所需的代码并设置自己的环境。

注意:您需要DevExpress的XPO和WinForms库的评估版本。您还需要Visual Studio 2010或更高版本。我没有测试过使用SharpDevelop,所以无法告诉您是否可行。

关注点

还有问题吗?是的,代码尚未实现授权。这对于真实应用程序来说也是一个重要功能。还需要进行更多研究才能实现实用且可用的机制。这是基于我使用XPO的情况,并且需要研究如何使用该库实现它。

同样,也没有代码利用多个STS之间的联合。所以我还有一些未来的工作。

历史

已更正的图像。它们未正确显示。

初始发布。

© . All rights reserved.