This article is outdated.
<WebLogin>service setting is no longer used. To enable web login you should include
<add key="ForceRemotingWebLogin" value="true" />in your web.config.
Service list xml defines Appframe services available in login form. You can define them as built in service list or as a url. Recommended way is to have service list url with fall back service list embeded in login executable. Below is minimal example.
Service list minimal definition
Building this project would result in no services defined just (manual), but UseWindowsAuthByDefault and UseLocalization would still
be used. If UseWindowsAuthByDefault is set to true then authentication will be set to windows authentication on first lauch. If
UseLocalization is set to true, current culture
and current UI culture will be set acordingly
You can define multiple Client nodes in your service list xml, but keep in mind that your login builder project name and Client name must match (see first image). This is so we can have single service list xml for multiple clients (like "MarketMaker" and "MarketMaker Dev").
Below we define service list xml with most options.
Extended service list definition
If you want to preselect specific service on first login launch, make sure DefaultService and Service/Name match. Same goes for FallbackServiceName and Service/Name.
<client> node elements explained below.
<Name>- Should directly map to login executable name. Login executable is named after login builder project name. This is important to get right!
<DefaultService>- Should maps to
<Name>value. When login for suns for the first time (or user session can not be loaded from registry) service defined here will be preselected and used first.
<UseWindowsAuthByDefault>- Accepts "true" or "false" values. When set to "true" and
<AuthenticationMode>service setting is not set to
SQLServerlogin form will use windows credentials to log in.
<UseLocalization>- Accepts "true" or "false" values. When set to "true" clinet application will apply culture and culture UI settings based on user settings from manage users.
<DisableSplashScreen>- Accepts "true" or "false" values. Only used when building core launcher. When set to "true" user will not be greeted with regular core launcher splash screen.
<WebLogin>- Accepts "true" or "false" values. When set to "true" user will be greeted with web login form as oposed to built in login screen. To load login screen
<RemotingHost>setting will be used. Make sure your remoting works before enabling web login.
<service> node elements explained below.
<Name>- Used to identify service from the list of others. Will be displayed in service selection dropdown.
<ClientName>- Name to be displayer when your main form launches (see no. 2 in image below). Also
UserSession.Service.ClientNameis filled with that value.
<Server>- Server part in your connection string to connect to your service. Also
UserSession.Service.Serveris filled with that value.
<Database>- Database or "Initial Catalog" part in your connection string to connect to your service. Also
UserSession.Service.Databaseis filled with that value.
<WebUrl>- Used to fill
UserSession.Service.WebUIUrl. that in turn is used when you use
CR3.OpenAppframePage(), Web UI Url is also used in password reset and two factor authentication functionality. This url should point to valid Appframe web site.
<RemotingHost>- Used to fill
UserSession.Service.RemotingHost. Should point to valid remoting host. Remoting in turn should be able to connect to your sql server instance.
<WinAuthRemotingHost>- Used to fill
UserSession.Service.WinAuthRemotingHost. Should point to valid windows authentication remoting host.
<WebAPIHost>- Used to fill
UserSession.Service.WebAPIHost. Will be used more in the future.
<MainProjectName>- This form will be opened when login succeds. Should be used when you are using custom "Sys.WinClient.Main" that is maintained by your self.
<ImproveApp>- Used to fill
UserSession.Service.ImproveApp. Application that will be opened when you press improve button (see no. 10 in image below).
<DocHost>- Used to fill
UserSession.Service.DocHost. This in turn is used to display help page when pressing "Help -> View Help" (see no.11 in image below).
<Responsible>- When reset password fails user will be advised to contact responsible via message box.
<AllowRememberPassword>- Accepts "true" or "false" values. When set to "true" and user logs in, he will be prompted to store password in windows credential store.
<AllowAutoLogin>- Accepts "true" or "false" values. When set to "true" and user checks "auto login" check box when prompted to save password, next time he will be logged in automatically. And he will have to use "File -> Log out" in main form to stop logging in automatically.
<FallbackServiceName> - When supplied should point to any other service name. If this service login fails it then tries switching to
fallback service and then tries loggin in again.
<WebLogin>is set to "true"
<FallbackServiceName>functionality will no longer work. To fall back to regular login when web login is enabled hold [Ctrl] + [Alt] when launching client.
<AuthenticationMode> - can be set to "SQLServer" or "IntegratedSecurity". Using this setting will override
<UseWindowsAuthByDefault> setting, and will be used per service. This setting should be used together with
<FallbackServiceName> to enable fallback to differnt authentication mode.
<LoginProjectName>- used by core launcher. That is the application name (from Win Projects) that will be launched by core launcher. If setting is not present defaults to "App.WinClient.Login". When this setting is present and building "Sys login to app login" that name will be used when preparing project to be inserted as Win Project.
Below is the picture showing how values form service list xml flow to actual usages.
Service list xml value flow.
Win Project (Usually in MSBuild namespace) that must contain settings.xml so Login builder can actually construct archive to be sent to build service. settings.xml contains vital 3 things:
BuildScript name attribute - points to a MSBuild file that should be called first.
dependencies - define additional content to be sent to build service. DatabaseProject can be child nodes of dependencies node. DatabaseProject node's attribute type directly maps to Appframe.ProjectHandling.eFileProjectType. Including projects to dependencies will include them in archive to be sent to build service
OutputDir - determines relative path to be archived and sent back.
<?xml version="1.0" encoding="utf-8"?> <settings> <BuildScript name ="build.proj" /> <OutputDir name ="Output" /> <dependencies> <DatabaseProject name ="Sys.WinClient.login" type="Application" /> <DatabaseProject name ="Sys.MSBuild.LoginClickOnce" type="Application" /> <DatabaseProject name ="Sys.MSBuild.Targets" type="Library" /> </dependencies> </settings>
When constructing entry point archive, folder structure will be preserved (will mirror your source control folder structure, see image below). And DevelopmentRuntime will always be included. EntryPointConfig.xml will always be included as it defines what needs to be done on build service side.
Entry Point archive structure
After defining settings.xml it is all up to your build.proj MSBuild script to reach your goal.
Notable MSBuild projects:
Article is based on Sys.MSBuild.Example.CustomLoginClickOnce implementation and all the source is available in this project.
I will explain how you can Implement custom ClickOnce build for Sys.WinClient.login. Changes to existing Login implementation:
At the same time we want to continue using Sys.WinClient.login and Sys.MSBuild.LoginClickOnce for our actual ClickOnce build. We just want to alter the actual login project. So we need to create another MSBuild project that is going to be used as Entry Point project for our build service. It will construct archive to be sent and will contain MSBuild script to kick off build on the other side. In addition to entry point project we have to define custom build steps in login builder. For our purpose it sufficient to just Insert ClickOnce steps and replace SetEntryProjName action value with your entry project name (in my case it is Sys.MSBuild.Example.CustomLoginClickOnce).
Setting custom build steps for your build project
We are done with defining steps for custom build. Now we need to implement our Entry Point project. In Win Projects form we press Create New.., Select appropriate namespace, supply a good name for a project, pick Sys.Template.BuildEntryPoint template and add description.
Creating Entry Point project
Now that a project is created, we have to write something in it! First and most important part is settings.xml as it defines what is actually going to be sent to build service. Things we must define:
BuildScript name attribute points to the MSBuild file that should be called first. In our example we leave it as it is. We are still going to call build.proj. dependencies define additional content to be sent to build service. We need 4 additional projects for that. Sys.WinClient.login is required by definition. You can not build login without login itself :). Next as we said we want to continue using existing (maintained) ClickOnce build project. We need to include Sys.MSBuild.LoginClickOnce and everything included inside its settings.xml, Sys.MSBuild.Targets will have to come along. Since we needed to alter our login by adding some dll reference and some content file, I chose Sys.Template.EmptyClassLibrary since it actually has dll and some files to be included. OutputDir determines relative path to be archived and sent back (and inserted as version in login builder history tab). Since Sys.MSBuild.LoginClickOnce is going to put all the build artifacts into "Output" folder we will change it accordingly.
settings.xml file content:
<?xml version="1.0" encoding="utf-8"?> <settings> <BuildScript name ="build.proj" /> <OutputDir name ="Output" /> <dependencies> <DatabaseProject name ="Sys.WinClient.login" type="Application" /> <DatabaseProject name ="Sys.MSBuild.LoginClickOnce" type="Application" /> <DatabaseProject name ="Sys.MSBuild.Targets" type="Library" /> <DatabaseProject name ="Sys.Template.EmptyClassLibrary" type="Library" /> </dependencies> </settings>
Now we are reaching essence of what is actually going to be done. We must update build.proj to actually alter Sys.WinClient.login and call original Sys.MSBuild.LoginClickOnce build script.
As we all know all the project files like .vbproj and .csproj (and .fsproj and .wixproj and..) are nothing more than MSBuild scripts. So in order to use custom build we need to know MSBuild to some extent. We need to do 2 things in our build script:
But at first we must define some properties inside our MSBuild script. It is generally accepted good practice to define local properties starting with "_" (underscore) although they are still going to be visible to all imported/used scripts. _loginprojpath points to folder where our login project is. _loginbuildfile points to MarketMaker.vbproj path. _outputpath is where our resulting artifacts will be placed.
<PropertyGroup> <_loginprojpath Condition="$(_loginprojpath) == ''" >..\..\WinProjects\Sys.WinClient.login</_loginprojpath> <_loginbuildfile Condition="$(_loginbuildfile) == ''">$(_loginprojpath)\MarketMaker.vbproj</_loginbuildfile> <_outputpath>$(MSBuildProjectDirectory)\..\..\..\Output\</_outputpath> </PropertyGroup>
Calling the original scrip is easy. Just import actual project file and define Target calling targets from Sys.MSBuild.LoginClickOnce. Inside Project node define Target and Import nodes:
<Target Name ="callclickoncebuild"> <CallTarget Targets="build" /> </Target> <Import Project="$(MSBuildThisFileDirectory)\..\Sys.MSBuild.LoginClickOnce\build.proj" />
Since we import Sys.MSBuild.LoginClickOnce\build.proj all the targets defined inside are now available and can be called using CallTarget. That is what we are doing in callclickoncebuild Target definition.
Now we need to somehow alter the MarketMaker.vbproj MSBuild script inside of Sys.WinClient.login. I have decided to go with XslTransformation MSBuild task. You supply original xml file, output destination and transformation file (in bellowed .xslt format). Currently MSBuild supports only version 1.0 of xslt format, but that is enough for our plan. We write output of our transformation to .tmp file since reading and writing to one file at the same time is not good idea at all :). Been there, done that. After transformation we overwrite original file with temp file content and delete the temp file (Copy and Delete MSBuild tasks).
<Target Name="injectdllref"> <Message Text="Injecting New dll reference and content file for your $(_loginbuildfile);" Importance="high" /> <XslTransformation XmlInputPaths="$(_loginbuildfile)" XslInputPath="$(MSBuildThisFileDirectory)transformations.xslt" OutputPaths="$(_loginbuildfile).tmp" /> <Copy SourceFiles="$(_loginbuildfile).tmp" DestinationFiles="$(_loginbuildfile)" /> <Delete Files="$(_loginbuildfile).tmp" /> </Target>
To make it more spicy we sneak in and copy some more content to our output path after all builds are finished. We define new target called postbuild. Inside we copy EntryPointConfig.xml that is generated in login builder and is used by build service. It is just an exercise and a proof that we can add some content to build output archive and nothing more.
<Target Name ="postbuild" > <Copy SourceFiles="..\..\EntryPointConfig.xml" DestinationFiles="$(_outputpath)\EntryPointConfig.xml" /> </Target>
Now that transformation is called (defined later, read on..) and original Sys.MSBuild.LoginClickOnce\build.proj build script is called (and sneaky file copy is done) we need to set ordering in place and what is called by default when building from this script.
It is clear that first we want to transform and then do the original build. We add attribute
DependsOnTargets="injectdllref" to callclickoncebuild target. postbuild on the other hand should be called last and after callclickoncebuild, so we add attribute
DependsOnTargets ="callclickoncebuild" to postbuild. We also need to define DefaultTargets for our project. Since calling postbuild will just chain all dependent targets we can supply it. We add attribute
DefaultTargets="postbuild" to our project xml node. That is it, we have completed our MSBuild script part.
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="postbuild" ToolsVersion="4.0"> <PropertyGroup> <_loginprojpath Condition="$(_loginprojpath) == ''" >..\..\WinProjects\Sys.WinClient.login</_loginprojpath> <_loginbuildfile Condition="$(_loginbuildfile) == ''">$(_loginprojpath)\MarketMaker.vbproj</_loginbuildfile> <_outputpath>$(MSBuildProjectDirectory)\..\..\..\Output\</_outputpath> </PropertyGroup> <!-- injecting login projects project file with aditional reference and some simple file --> <Target Name="injectdllref"> <Message Text="Injecting New dll reference and content file for your $(_loginbuildfile);" Importance="high" /> <XslTransformation XmlInputPaths="$(_loginbuildfile)" XslInputPath="$(MSBuildThisFileDirectory)transformations.xslt" OutputPaths="$(_loginbuildfile).tmp" /> <Copy SourceFiles="$(_loginbuildfile).tmp" DestinationFiles="$(_loginbuildfile)" /> <Delete Files="$(_loginbuildfile).tmp" /> </Target> <!-- calling the actual click once build --> <Target Name ="callclickoncebuild" DependsOnTargets="injectdllref"> <CallTarget Targets="build" /> </Target> <!--copy some file to output folder after it is built.--> <Target Name ="postbuild" DependsOnTargets ="callclickoncebuild" > <Copy SourceFiles="..\..\EntryPointConfig.xml" DestinationFiles="$(_outputpath)\EntryPointConfig.xml" /> </Target> <!-- importing click once build script from "Sys.MSBuild.LoginClickOnce" --> <Import Project="$(MSBuildThisFileDirectory)\..\Sys.MSBuild.LoginClickOnce\build.proj" /> </Project>
Last thing remaining. We need to define actual project file transformation. Oh boy, oh boy.. xslt transformations. Not So fast. Lets ease our task by using tools! We will use Win Merge to help us.
Now just compare the 2 folders using Win merge (select both folders -> right click -> win merge).
We see that changes are only made in project file so we will actually get away with a simple xml transformation. We will define 3 teamplate rules:
For full copy we match all attributes or nodes and copy them. For Reference node inclusion we match ItemGroup nodes with Project parent node having Reference child Node (
match="b:Project/b:ItemGroup[.//b:Reference]"). It is important to supply namespace in match attribute. Other way templates will not match. There for in xsl:stylesheet node we add namespace definition attribute (
xmlns:b="http://schemas.microsoft.com/developer/msbuild/2003"). Now we can use b:NodeName to match node of MSBuild namespace. Continuing on template definition we copy all the content and just add reference node after all other template content. Same technique applies to content file inclusion. In this case we match ItemGroup nodes with Project parent node having Content child node (
match="b:Project/b:ItemGroup[.//b:Content]"). Template content is almost the same. we just add appropriate MSBuild Content node instead of reference. One thing you might notice that actually long relative path to content file is supplied instead of just file name . It is because when we included content file in login project it also copied the file over. We did not in our build script. So we supply the actual relative path to file (
When adding inline nodes (like we did with Reference and Content MSBuild nodes) we must supply namespace in it (
xmlns="http://schemas.microsoft.com/developer/msbuild/2003") because we want our nodes to be of the same namespace as whole document. At the same time we also add attribute
exclude-result-prefixes ="b" to xsl:stylesheet because we do not need namespaces to be defined explicitly in those nodes. Here is what we get.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:b="http://schemas.microsoft.com/developer/msbuild/2003" exclude-result-prefixes ="b"> <!-- "xmlns:b" is required since all the msbuild files are of that namespace. But at the same time we do not this namespace in resulting elements, "exclude-result-prefixes" serves this purposes. --> <!--full copy--> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()" /> </xsl:copy> </xsl:template> <!--Injecting dll reference--> <xsl:template match="b:Project/b:ItemGroup[.//b:Reference]"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> <Reference Include="Sys.Template.EmptyClassLibrary" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <HintPath>..\..\Libraries\Sys.Template.EmptyClassLibrary\bin\Release\Sys.Template.EmptyClassLibrary.dll</HintPath> </Reference> </xsl:copy> </xsl:template> <!-- Injecting content only file --> <xsl:template match="b:Project/b:ItemGroup[.//b:Content]"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> <Content Include="..\..\Libraries\Sys.Template.EmptyClassLibrary\bin\Release\Sys.Template.EmptyClassLibrary.xml" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </xsl:copy> </xsl:template> </xsl:stylesheet>
Now we can go to login builder and try out our custom build.
It is not mandatory to do exactly as described in this article. If more then trivial reference inclusion is required, you could just branch system login to some other project (taking responsibility of maintaining it, and DO NOT use App.WinClient.Login it is reserved for core launcher click once) and implement custom build using your version of login project all together. It will be easier at first since you will not have to do any kind of transformations but you will have to maintain your login project (harder in the future). When going this path do not forget to change actual login path property in your MSBuild file:
<PropertyGroup> <!--path Properties--> <LoginProjectPath>..\..\WinProjects\App.WinClient.CustomLogin</LoginProjectPath> <!--some other properties ...--> </PropertyGroup>
Hope this will help you.
If you feel this article can be improved, do not hesitate and contact me (English is not my native language).
You can now build installer packages for your Pims Client via Login builder project.