Is it possible to change the update URL to different location of an installed ClickOnce application? If so, how can I do that?
You mention in your comment that you wish to change it "on the client side". This is not possible. Your client app must be able to check for the update at the previous location which will then redirect it to the new location for the immediately next deployment.
See How to move a ClickOnce deployment.
Is it possible with a trick.
You can deploy it to the default publish location. (the application shouldn't check for updates).
Then copy your deployment to the customers server.
Just install your application on the client machines.
The field System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri contains the location and .application where the application is installed from. If you know that, then you can simple execute this url.
To check if there is an update, examine the .application file en grape the version.
this is my helper class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
namespace MatemanSC.Utility
{
public class ClickOnceUtil
{
Version _UpdateVersion = null;
public string UpdateLocation
{
get
{
return System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri;
}
}
public Version AvailableVersion
{
get
{
if (_UpdateVersion == null)
{
_UpdateVersion = new Version("0.0.0.0");
if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
{
using (XmlReader reader = XmlReader.Create(System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri))
{
//Keep reading until there are no more FieldRef elements
while (reader.ReadToFollowing("assemblyIdentity"))
{
//Extract the value of the Name attribute
string versie = reader.GetAttribute("version");
_UpdateVersion = new Version(versie);
}
}
}
}
return _UpdateVersion;
}
}
public bool UpdateAvailable
{
get
{
return System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion != AvailableVersion;
}
}
public string CurrentVersion
{
get
{
return System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
}
}
public void Update()
{
System.Diagnostics.Process.Start(System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri);
Environment.Exit(0);
}
public void CheckAndUpdate()
{
try
{
if (UpdateAvailable)
Update();
}
catch (Exception)
{
}
}
}
}
And this how to use it:
public partial class App : Application
{
public App()
{
ClickOnceUtil clickonceutil = new ClickOnceUtil();
clickonceutil.CheckAndUpdate();
}
}
When you want to change the url that you will use to upgrade programs, you can just use url rewrite at web.config: the old program will point to the old url, but it will bring the new program, which will have the new url.
Related
I am building an addin for Autodesk's Revit.
I created a class library .NET Framework project called neo4jTest.
I'm trying to use the Neo4j API to communicate with the DB and save data to it.
I try to following:
Compile the .dll, and place the .dll file and .addin file in the
Revit folder which can load it.
Start Revit, and click "Load Once" for the addin approval.
Click the Button I've created (in the appropriate Tab)
Error appears (image below)
Run the .dll directly from the add-in manager -> no error.
Click the Button again -> no error.
I am expecting:
No error, and that both the direct execution of the .dll, and the Button, to perform the same action.
Neo4j DB logs the data being sent. Currently I can't see the data is actually being updated.
Is Revit first loading the assembly locally or caching it in some way when I run the .dll directly and then the loaded addin can use it because it is available?
Would appreciate any advice on how to go around this or solve it.
Thank you
CODE:
Main.cs file for button creation:
using Autodesk.Revit.UI;
using System.Reflection;
namespace neo4jTest
{
public partial class Main : IExternalApplication
{
public UIControlledApplication _application;
public Result OnStartup(UIControlledApplication application)
{
_application = application;
string tabName = "Neo4jTest";
application.CreateRibbonTab(tabName);
RibbonPanel ribbonPanel = application.CreateRibbonPanel(tabName, "Neo4jTest");
string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
InitializeButtons(ribbonPanel, thisAssemblyPath);
return Result.Succeeded;
}
private void InitializeButtons(RibbonPanel ribbonPanel, string thisAssemblyPath)
{
CreateButton(ribbonPanel, "neo4jTest", "neo4jTest", thisAssemblyPath, "neo4jTest.Class1", "neo4jTest");
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
private void CreateButton(
RibbonPanel ribbonPanel,
string name,
string text,
string thisAssemblyPath,
string className,
string toolTip
)
{
PushButtonData buttonData = new PushButtonData(name, text, thisAssemblyPath, className);
PushButton pushButton = ribbonPanel.AddItem(buttonData) as PushButton;
pushButton.ToolTip = toolTip;
}
}
}
Neo4jConnection.cs for establishing DB data:
using Autodesk.Revit.UI;
using Neo4j.Driver;
using System;
using System.Threading;
namespace neo4jTest
{
public static class Neo4jConnection
{
public static IDriver _driver;
private static readonly string uri = "myUri";
private static readonly string user = "myUser";
private static readonly string password = "myPassword";
public static void SaveToDB()
{
try
{
_driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password));
var session = _driver.AsyncSession();
var data = session.ExecuteWriteAsync(async tx =>
{
var result = await tx.RunAsync("CREATE (n:Testing) " +
"SET n.fulltext = testing text " +
"SET n.username = userTest " +
"RETURN n"
);
return await result.ToListAsync();
});
Thread.Sleep(500);
session.Dispose();
}
catch (Exception ex)
{
TaskDialog.Show("Error", ex.StackTrace);
}
}
}
}
Class1 for executing button:
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace neo4jTest
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class Class1 : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
Neo4jConnection.SaveToDB();
TaskDialog.Show("Validate", "Done");
return Result.Succeeded;
}
}
}
Preface
I have created an ASP.NET Code Web API using Visual Studio 2022 to familiarise myself with the topic of "Web APIs".
I have converted the project to .NET 5.
In the MyControllers.cs I have so far code for a Get and for a Post request.
I have removed the WeatherForecast.cs created by Visual Studio. I have also set in the launchSettings.json that the browser jumps to MyController from the beginning.
To practice Dependency Injection, I added another project called TextRepository (a class library) to the assembly. I had written a text file with another Visual Studio project that contains the numbers from 0–99. This text file is read in and returned in the Get method of MyController.cs. Now the numbers are displayed in the browser when called. I had also included the interface INumberRepository.
To practise the whole thing again, I created another repository: ImageRepository. The aim is to find all pictures in the folder Pictures and store them in a List. To do this, I downloaded System.Drawing.Common from the NuGet package manager.
Question for you:
I am still struggling a bit to request the API in the browser for different purposes. I still want to use the call https://localhost:44355/api/My for displaying the numbers in the browser, i.e., the Get method. How can I make it so that I use a different link to transfer the image data? I am concerned with the call to the API – do I have to write a second Get function? – And about transferring the bytes of the images.
If anyone wonders what the images are: I have created 2 test images for this purpose (1920 pixels ×1080 pixels).
WebApplication2
in Startup.cs
services.AddScoped<TextRepository.INumberRepository, TextRepository.NumberTextFileRepository>();
services.AddScoped<ImageRepository.IImageTransferRepository, ImageRepository.ImageTransferRepository>();
MyController.cs
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using TextRepository;
using ImageRepository;
namespace WebApplication2.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly INumberRepository numberRepository;
private readonly IImageTransferRepository imageRepository;
public MyController(INumberRepository numberRepository, IImageTransferRepository imageTransferRepository)
{
this.numberRepository = numberRepository;
this.imageRepository = imageTransferRepository;
}
[HttpGet]
public ActionResult<List<int>> Get()
{
List<int> numbers = this.numberRepository.GetNumbers();
List<System.Drawing.Bitmap> foundImages = this.imageRepository.GetImages();
return Ok(numbers);
}
[HttpPost]
public IActionResult Post([FromBody] DataTransferObject transferObject)
{
return Ok(transferObject.PassedString + $" {transferObject.Zahl}");
}
}
}
TextRepository
TextFileParser.cs
using System;
using System.Collections.Generic;
using System.IO;
namespace TextRepository
{
public class TextFileParser
{
public static List<int> ReadAllData(string path)
{
if (!File.Exists(path))
{
throw new FileNotFoundException(path);
}
string[] allLines = System.IO.File.ReadAllLines(path);
if (allLines == null)
{
throw new Exception("War null");
}
if (allLines.Length < 1)
{
throw new Exception("Die Datei enthält 0 Zeilen.");
}
if (allLines.Length == 1)
{
return new List<int>();
}
List<int> numbers = new List<int>();
for (int i = 1; i < allLines.Length; i++)
{
if (int.TryParse(allLines[i], out int result))
{
numbers.Add(result);
}
else
{
Console.WriteLine($"In der Textdatei, in Zeile {i + 1}, ist eine inkorrekte Zahl aufgetreten.");
continue;
}
}
return numbers;
}
}
}
NumberTextFileRepository
using System.Collections.Generic;
namespace TextRepository
{
public class NumberTextFileRepository : INumberRepository
{
public List<int> GetNumbers()
{
System.IO.DirectoryInfo Root = new System.IO.DirectoryInfo(System.IO.Directory.GetCurrentDirectory());
return TextFileParser.ReadAllData(Root.Parent.FullName + "\\Textdatei.txt");
}
}
}
INumberRepository.cs
using System.Collections.Generic;
namespace TextRepository
{
public interface INumberRepository
{
List<int> GetNumbers();
}
}
ImageTransferRepository
ImageTransferRepository.cs
using System;
using System.Collections.Generic;
namespace ImageRepository
{
public class ImageTransferRepository : IImageTransferRepository
{
public List<System.Drawing.Bitmap> GetImages()
{
return ImageTransfer.GetImagesFromFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));
}
}
}
ImageTransfer.cs
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ImageRepository
{
public class ImageTransfer
{
public static List<System.Drawing.Bitmap> GetImagesFromFolder(string path)
{
if (!Directory.Exists(path))
{
throw new FileNotFoundException(path);
}
List<FileInfo> fileInfos = new List<FileInfo>();
fileInfos.AddRange(new DirectoryInfo(path).EnumerateFiles().Where(f => IsValidFile(f)));
fileInfos = fileInfos.OrderBy(x => x.CreationTime).ToList(); // The newest image should be at the top of the list.
return (from FileInfo file in fileInfos select new System.Drawing.Bitmap(file.FullName)).ToList();
}
private static bool IsValidFile(FileInfo File)
{
return File.FullName.ToLower().EndsWith(".bmp") ^ File.FullName.ToLower().EndsWith(".jpeg") ^ File.FullName.ToLower().EndsWith(".jpg") ^ File.FullName.ToLower().EndsWith(".png");
}
}
}
IImageTransferRepository.cs
using System.Collections.Generic;
using System.Drawing;
namespace ImageRepository
{
public interface IImageTransferRepository
{
List<Bitmap> GetImages();
}
}
I still want to use the call https://localhost:44355/api/My for displaying the numbers in the browser, i.e., the Get method. How can I make it so that I use a different link to transfer the image data? I am concerned with the call to the API – do I have to write a second Get function? – And about transferring the bytes of the images.
If you want to return List<int> numbers and List<System.Drawing.Bitmap> foundImages together,you can try to create a class which contains the two lists.Or you need to write a second Get function.
public class TestModel {
public List<int> numbers { get; set; }
public List<System.Drawing.Bitmap> foundImages { get; set; }
}
Get function:
[HttpGet]
public ActionResult<TestModel > Get()
{
TestModel t=new TestModel();
t.numbers = this.numberRepository.GetNumbers();
t.foundImages = this.imageRepository.GetImages();
return Ok(t);
}
If you want to create a second Get function,here is the demo:
[HttpGet]
public ActionResult<List<int>> Get()
{
List<int> numbers = this.numberRepository.GetNumbers();
return Ok(numbers);
}
[HttpGet("foundImages")]//route will be https://localhost:44355/api/My/foundImages
public ActionResult<List<System.Drawing.Bitmap>> Get()
{
List<System.Drawing.Bitmap> foundImages = this.imageRepository.GetImages();
return Ok(foundImages);
}
I am breaking down the testing on this so that I have feature files for areas like Login, ResetPassword, ForgotPassword etc. Let's say I have the below example. I have an automation step creating a brand new user in CreateAccount.feature. That step is used multiple times within that Feature/Step Class without issue. But now I want the user to change their password so I create a new Feature File MyAccount.feature. When I copy the Given Statement in, it is found immediately. Then I add the code to click the reset password and continue on with the rest of the steps.
When I run the ResetPassword test, the automation creates the new user but when it get's to step 2, "When I Click Reset Password" it fails because it can't find the element. Since bindings are global, this strikes me odd. So what I did was take step "Given I have created my account" and renamed it and added to the other feature file/steps class and ran it again. It worked fine.
I am not sure why I can't share between steps. Any ideas?
Some updates showing more code...
CreateAccount.feature
scenario: Feature Create Account
Given I have created my account
-----------
CreateAccountsteps.cs
namespace Project
{
[Binding]
public class CreateAccount: BaseTestObject
{
[Given]
public void Given_I_have_created_my_account()
{
ConfigProperties.Environment = "Test";
TestDriver.goToUrl(ConfigProperties.StartUrl);
TestDriver.goToUrl(ConfigProperties.StartUrl + "Create/Account");
[followed by input for creating a user acct]
-------------------------------------------------
MyAccount.feature
scenario: Feature Change Password
Given I have created my account
When I Click Reset Password
...........
MyAccountSteps.cs
namespace Project
{
[Binding]
public class MyAccountSteps: BaseTestObject
{
[When]
public void When_I_click_Reset_Password()
{
On.MyHeaderPage.BtnResetPassword.Click();
}
[followed by rest of steps to change password]
BaseTestObject.cs
namespace Project
{
public class BaseTestObject
{
private IWebDriver seleniumDriver;
private IDriver testDriver;
[TestInitialize]
public virtual void Setup()
{
TestDriver.goToUrl(ConfigProperties.StartUrl);
}
[AfterScenario]
public void CleanUp()
{
if (seleniumDriver != null)
{
SeleniumDriver.Dispose();
seleniumDriver = null;
}
}
public IWebDriver SeleniumDriver
{
get
{
if (seleniumDriver == null)
{
seleniumDriver = GetDriver();
}
return seleniumDriver;
}
}
public IDriver TestDriver
{
get
{
if (testDriver == null)
{
testDriver = new UiDriver(SeleniumDriver);
}
return testDriver;
}
}
public CurrentPageObjectScope On
{
get
{
return new CurrentPageObjectScope(TestDriver);
}
}
public static String GetTimestamp()
{
return DateTime.Now.ToString("yyyyMMddhhmmssfff");
}
public static String GetTimestamp2()
{
return DateTime.Now.ToString("M/d/yyyy");
}
private IWebDriver GetDriver()
{
switch (ConfigProperties.Browser.ToLower())
{
case "firefox":
return new FirefoxDriver();
case "chrome":
ChromeOptions options = new ChromeOptions();
ChromeDriverService service = ChromeDriverService.CreateDefaultService(#"../Chrome/");
service.SuppressInitialDiagnosticInformation = true;
service.HideCommandPromptWindow = true;
options.AddArguments("test-type");
options.AddArgument("--start-maximized");
return new ChromeDriver(service, options);
case "ie":
case "internetexplorer":
return new InternetExplorerDriver(#"../IE/");
default:
throw new NotImplementedException("Unknown browser string in Config properties " + ConfigProperties.Browser);
}
}
Based on your updates it looks like you have named your when step incorrectly. You feature says:
scenario: Feature Change Password
Given I have created my account
When I Click Reset Password
but you step has the name When_I_click_My_Account
This seems wrong to me.
Really though we need more details (like the actual exception message) and perhaps some indication of what BaseTestObject looks like.
Resolved - In the BaseTestObject I changed the methods to static.
I'm trying to compile small fragments of C# into JavaScript using the Script# compiler.
But I don't get anything in return, GetStream() in my MemoryStreamSource is not even being called, so I must be doing something wrong.
Here's my code:
CodeScriptCompiler csc = new CodeScriptCompiler();
return csc.CompileCSharp("String.IsNullOrWhiteSpace(Model.MobilePhoneNumber)");
CodeScriptCompiler.cs
using System;
using System.Collections.Generic;
using ScriptSharp;
namespace CodeToScriptCompiler
{
public class CodeScriptCompiler
{
ScriptCompiler sc = new ScriptCompiler();
public string CompileCSharp(string csharpCode)
{
string errorMessages = String.Empty;
CompilerOptions options = new CompilerOptions();
options.Defines = new List<string>();
options.References = new List<string>();
options.References.Add("System.dll");
options.Resources = new List<IStreamSource>();
options.Sources = new List<IStreamSource>();
options.Sources.Add(new MemoryStreamSource(csharpCode));
options.TemplateFile = new MemoryStreamSource(csharpCode);
MemoryStreamDestination output = new MemoryStreamDestination();
options.ScriptFile = output;
if (!options.Validate(out errorMessages))
{
return errorMessages;
}
return output.GetCompiledCode();
}
}
}
MemoryStreamSource.cs
using System.IO;
using System.Text;
using ScriptSharp;
namespace CodeToScriptCompiler
{
public class MemoryStreamSource : IStreamSource
{
private string _code;
private MemoryStream _memoryStream;
public MemoryStreamSource(string code)
{
this._code = code;
}
public string Name
{
get { return "InMemoryCode"; }
}
public string FullName
{
get { return "InMemoryCode"; }
}
public void CloseStream(Stream stream)
{
stream.Close();
}
public Stream GetStream()
{
this._memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(this._code));
return this._memoryStream;
}
}
}
MemoryStreamDestination.cs
using System;
using System.IO;
using ScriptSharp;
namespace CodeToScriptCompiler
{
public class MemoryStreamDestination : IStreamSource
{
private MemoryStream _memoryStream;
private string _compiledCode;
public string Name
{
get { return "MemoryStreamDestination"; }
}
public string FullName
{
get { return "MemoryStreamDestination"; }
}
public void CloseStream(Stream stream)
{
if (String.IsNullOrWhiteSpace(this._compiledCode))
{
this._compiledCode = this.GetCompiledCode();
}
stream.Close();
}
public Stream GetStream()
{
this._memoryStream = new MemoryStream();
return this._memoryStream;
}
public string GetCompiledCode()
{
if (!String.IsNullOrWhiteSpace(this._compiledCode))
{
return this._compiledCode;
}
if (this._memoryStream != null)
{
using (StreamReader sr = new StreamReader(this._memoryStream))
{
return sr.ReadToEnd();
}
}
return String.Empty;
}
}
}
Some things I see potentially problematic.
TemplateFile is set to a c# code stream. Leave it unset, since that is not a valid template.
References should include the script# mscorlib, and furthermore, only full paths to valid script# assemblies. System.dll is not a script# assembly.
Before you read from the MemoryStream, you need to set the stream position back to the start, otherwise it is at the end after the compiler has written to it, and there is nothing more to read.
Not seeing a call to Compile on the Compiler instance you created, passing in the options instance. My guess is you did do that, just not there in the stack overflow snippet.
You probably should also implement IErrorHandler and pass that to the compiler to get error messages should they occur, once you have the basic thing working.
For reference you can also look at the unit tests at https://github.com/nikhilk/scriptsharp/tree/master/tests/ScriptSharp/Core which does something similar.
Note that you'll need a valid c# source file, rather than a single standalone expression. You can however likely deal with that by stripping off stuff from the start and end of the resulting script to get the script for just the expression you care about.
Hope that helps.
I am certainly interested/curious to understand how you're using this, and where you're compiling c# to script dynamically...
I want to check programmatically that the latest version of my Windows Service is installed. I have:
var ctl = ServiceController.GetServices().Where(s => s.ServiceName == "MyService").FirstOrDefault();
if (ctl != null) {
// now what?
}
I don't see anything on the ServiceController interface that will tell me the version number. How do I do it?
I am afraid there is no way other than getting the executable path from the registry as ServiceController does not provide that information.
Here is a sample I had created before:
private static string GetExecutablePathForService(string serviceName, RegistryView registryView, bool throwErrorIfNonExisting)
{
string registryPath = #"SYSTEM\CurrentControlSet\Services\" + serviceName;
RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(registryPath);
if(key==null)
{
if (throwErrorIfNonExisting)
throw new ArgumentException("Non-existent service: " + serviceName, "serviceName");
else
return null;
}
string value = key.GetValue("ImagePath").ToString();
key.Close();
if(value.StartsWith("\""))
{
value = Regex.Match(value, "\"([^\"]+)\"").Groups[1].Value;
}
return Environment.ExpandEnvironmentVariables(value);
}
After getting the exe path, just use FileVersionInfo.GetVersionInfo(exePath) class to get the version.
If you own the service, you can put version information into the DisplayName, e.g. DisplayName="MyService 2017.06.28.1517". This allows you to find an existing installation of your service and parse the version information:
var ctl = ServiceController
.GetServices()
.FirstOrDefault(s => s.ServiceName == "MyService");
if (ctl != null) {
// get version substring, you might have your own style.
string substr = s.DisplayName.SubString("MyService".Length);
Version installedVersion = new Version(substr);
// do stuff, e.g. check if installed version is newer than current assembly.
}
This may be useful if you want to avoid the registry. The problem is, that service entries can go to different parts of the registry depending on the installation routine.
If you are talking about getting the current version of your service automatically from the assembly properties then you can set up a property such as below in your ServiceBase class.
public static string ServiceVersion { get; private set; }
Then in your OnStart method add the following...
ServiceVersion = typeof(Program).Assembly.GetName().Version.ToString();
Full Example
using System.Diagnostics;
using System.ServiceProcess;
public partial class VaultServerUtilities : ServiceBase
{
public static string ServiceVersion { get; private set; }
public VaultServerUtilities()
{
InitializeComponent();
VSUEventLog = new EventLog();
if (!EventLog.SourceExists("Vault Server Utilities"))
{
EventLog.CreateEventSource("Vault Server Utilities", "Service Log");
}
VSUEventLog.Source = "Vault Server Utilities";
VSUEventLog.Log = "Service Log";
}
protected override void OnStart(string[] args)
{
ServiceVersion = typeof(Program).Assembly.GetName().Version.ToString();
VSUEventLog.WriteEntry(string.Format("Vault Server Utilities v{0} has started successfully.", ServiceVersion));
}
protected override void OnStop()
{
VSUEventLog.WriteEntry(string.Format("Vault Server Utilities v{0} has be shutdown.", ServiceVersion));
}
}
In the example above my event log displays the current version of my service...