Path to static file under wwwroot in Razor Pages - asp.net-core

What are the differences between these 3 methods used to get the path to the image stored under wwwroot? Seems like all work the same in my case, but would like to understand if there are any other case specific differences between them or benefits of using one over another.
I use this path to subsequently load the image into Bitmap MyBitmap variable for further processing. Would like it to be environment resilient whatever it is finally deployed to Windows, Linux or container; locally or in the cloud.
Using Razor Pages with ASP.NET Core 3.0.
public class QRCodeModel : PageModel
{
private readonly IHostEnvironment hostingEnvironment;
public QRCodeModel(IHostEnvironment environment)
{
this.hostingEnvironment = environment;
}
public void OnGet()
{
string TempPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "img", "Image1.png");
string TempPath1 = Path.Combine(Environment.CurrentDirectory, "wwwroot", "img", "Image1.png");
string TempPath2 = Path.Combine(hostingEnvironment.ContentRootPath, "wwwroot", "img", "Image1.png");
}
}

There is another option:
string TempPath4 = Path.Combine(hostingEnvironment.WebRootPath, "img", "Image1.png");
WebRootPath returns the path to the wwwroot folder.
This is recommended over using the first two options as they may not return the location that you want: Best way to get application folder path

Related

Create link to server controller's action in BlazorWeb assembly

I'm writing an app using WebAssembly Blazor hosted by ASP.NET Core. Some of pages are implemented in Blazor, but some old pages are still ASP.NET Core Razor views. I need to create a link in Blazor component pointing to action of controller on server.
I can write:
NavigationManager.NavigateTo("SomeContoller/SomeAction/123", true)
But I don't want to hardcode url to action, because changing server routing or contoller/action names will break such links. Is there any way to create proper links via some helper, similar to ASP.Net Core UriHelper? Like:
UriHelper.Action("SomeAction", "SomeController", new {id = 123});
In Blazor server apps you can use LinkGenerator. The usage is not much different that of UriHelper:
#using Microsoft.AspNetCore.Routing
#inject LinkGenerator LinkGenerator
Sign in
ReSharper understands this one too, so you will get auto-completion for controller and action names.
In WebAssembly apps LinkGenerator is not available, so your best bet is to dump all routes from the server and implement your own link generator which uses that data on the client (its complexity depends on complexity of your routes, the one from ASP.NET Core is quite complex).
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
namespace BlazorTest.Server.Controllers
{
[Route("api/routes")]
[ApiController]
public class RouteInformationController : ControllerBase
{
private readonly EndpointDataSource _endpointDataSource;
public RouteInformationController(EndpointDataSource endpointDataSource)
{
_endpointDataSource = endpointDataSource;
}
public IEnumerable<object> Get()
{
foreach (var endpoint in _endpointDataSource.Endpoints.OfType<RouteEndpoint>())
{
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
if (actionDescriptor == null)
continue;
yield return new
{
actionDescriptor.ControllerName,
actionDescriptor.ActionName,
Parameters = actionDescriptor.Parameters.Select(p => p.Name),
RoutePattern = endpoint.RoutePattern.RawText,
};
}
}
}
}
You can create a static class with constant properties in it for all the URLs that you use in the app. After that use the same static class property in both the page route and your navigation route. Below is a very basic version of this:
public static class RouteUrls
{
public static string Home = "/Home";
public static string ProductList = "/Product";
public static string ProductDetail = "/Product/Detail";
public static string SomePage = "/SomeContoller/SomeAction";
}
// to access it use like this:
NavigationManager.NavigateTo($"{RouteUrls.SomePage}/123", true)

Arquillian ShrinkWrap how to add an asset to the file system path

I am importing a library that reads from the file system instead of my web archive's resource folder. I want to be able to essentially mock that file by adding an asset with that path using ShrinkWrap, so I can run tests on my build server without guaranteeing the file system has all these files. I tried to add a String Asset in the appropriate path, but the code can't find that asset. Here's an example of what I'm trying to achieve.
Rest Resource
#Path("/hello-world")
public class HelloWorldResource {
#GET
public Response getHelloWorld(){
return Response.ok(getFileContent()).build();
}
private String getFileContent() {
StringBuilder builder = new StringBuilder();
try {
BufferedReader bufferedReader = new BufferedReader(
new FileReader(
"/usr/myFile.txt"));
String line = bufferedReader.readLine();
while (line != null) {
builder.append(line);
line = bufferedReader.readLine();
}
}
catch (Exception e) {
e.printStackTrace();
}
return builder.toString();
}
}
Test
#RunWith(Arquillian.class)
public class HelloWorldResourceTest {
#Deployment
public static WebArchive createDeployment()
{
WebArchive webArchive = ShrinkWrap
.create(WebArchive.class)
.addPackages(true,
HelloWorldApplication.class.getPackage(),
HelloWorldResource.class.getPackage(),
Hello.class.getPackage())
.add(new StringAsset("Blah"),"/usr/myFile.txt")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
System.out.println("WebArchive: " + webArchive.toString(true));
return webArchive;
}
#Test
#RunAsClient
public void testHello(
#ArquillianResteasyResource("hello-world") final WebTarget webTarget)
{
final Response response = webTarget
.request(MediaType.APPLICATION_JSON)
.get();
String hello = response.readEntity(String.class);
System.err.println("Hello: " + hello);
Assert.assertEquals("Status is not OK", response.getStatus(), 200);
}
}
Web Archive toString
/WEB-INF/
/WEB-INF/classes/
/WEB-INF/classes/com/
/WEB-INF/classes/com/
/WEB-INF/classes/com/
/WEB-INF/classes/com/helloworld/
/WEB-INF/classes/com/helloworld/application/
/WEB-INF/classes/com/helloworld/application/HelloWorldApplication.class
/WEB-INF/classes/com/helloworld/resource/
/WEB-INF/classes/com/helloworld/resource/HelloWorldResourceTest.class
/WEB-INF/classes/com/helloworld/resource/HelloWorldResource.class
/WEB-INF/classes/com/helloworld/dataobjects/
/WEB-INF/classes/com/helloworld/dataobjects/Hello.class
/WEB-INF/beans.xml
/usr/
/usr/myFile.txt
I get the following error:
java.io.FileNotFoundException: /usr/myFile.txt (No such file or
directory)
Seems like ShrinkWrap is adding /usr/myFile.txt as a relative path within the archive instead of making it seem like /usr/myFile.txt is at the root directory of my file system. Is there any way I can get ShrinkWrap to do what I want?
Shrinkwrap is intended to create archives, so the API is scoped to create assets within the archive you are creating. If you want to have resources created in the regular filesystem simply use JDK, there is nothing Shrinkwrap could help you with.
Alternatively, if possible, change your resource to read resources from the classpath, not filesystem path. With this approach, you can easily swap content for the test using Shrinkwrap as you are trying now with your example.

Wicket Deployment mode map resources wrong way

I have Page
getRootRequestMapperAsCompound().add(new NoVersionMapper("/card/${cardId}", CardPage.class));.
On this page there is TinyMCE4 editor. Which try to load images using relative path "images/1.jpg"
I've added resource mapping to allow images successfuly loaded.
mountResource("/card/image/${imageId}", imageResourceReference);
In DEVELOPMENT mode everything work nice, image are loaded in to editor, but in DEPLOYMENT mode, Page has been called twice, first time for /card/1 and second time for /card/image/1.jpg.
How to correctly mount resources for DEPLOYMENT mode?
UPDATE look like found the reason
public int getCompatibilityScore(Request request)
{
return 0; // pages always have priority over resources
}
, but then the question is: "Why it is working nice in development mode"?
Update 2 I haven't find better solution then add my own Resource Mapper with overrided getCompatibilityScore()
public class ImageResourceMapper extends ResourceMapper {
private String[] mountSegments;
public ImageResourceMapper(String path, ResourceReference resourceReference) {
super(path, resourceReference);
mountSegments = getMountSegments(path);
}
public ImageResourceMapper(String path, ResourceReference resourceReference, IPageParametersEncoder encoder) {
super(path, resourceReference, encoder);
mountSegments = getMountSegments(path);
}
#Override
public int getCompatibilityScore(Request request) {
if (urlStartsWith(request.getUrl(), mountSegments)) {
return 10;
}
return 0;
}
}

Using a custom RazorViewEngine AND RazorGenerator precompiled views

I am trying to use a custom (derived) RazorViewEngine AND precompiled views using RazorGenerator.
Some context:
We have a base product that we use for multiple client implementations. With that we have a core set of base views. Most of the views work most of the time. Right now we end up copying existing views for each new solution and modifying as needed. This ends up with 95% of the views being the same between clients and 5% changed.
What I want to do take a base set of views, compile them into a DLL and re-use it across clients. So far I have that working well using RazorGenerator.
Now the next step is to allow for customization (overrides) of views. There is a caveat though. Our application has two "modes" that a user is in. The mode they are in could require a different view.
I have created a derived class from the RazorGeneratorView. This view basically inspects the "OrderingMode" from a UserProfile object that Autofac resolves. Based on the mode - the Path Locator is replaced for the view resolution.
The idea being individual client applications will attempt to resolve the view first in the traditional Views folder. Only I am adding in a sub-directory of Views/{OrderingMode}/{Controller}/{View}.cshtml.
If the view is not found - then it will look in the compiled library (the core views).
This allows me to override individual views / partials as need be for clients.
public PosViewEngine() : base()
{
//{0} = View Name
//{1} = ControllerName
//{2} = Area Name
AreaViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/{1}/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core assembly
"Areas/{2}/Views/{1}/{0}.cshtml"
};
//Same format logic
AreaMasterLocationFormats = AreaViewLocationFormats;
AreaPartialViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/Partials/{PartialViewName}.cshtml
"Areas/{2}/Views/%1/{1}/Paritals/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core
"Areas/{2}/Views/{1}/{0}.cshtml"
};
ViewLocationFormats = new[]
{
"Views/%1/{1}/{0}.cshtml",
"Views/%1/Shared/{0}.cshtml",
"Views/{1}/{0}.cshtml",
"Views/Shared/{0}.cshtml"
};
MasterLocationFormats = ViewLocationFormats;
PartialViewLocationFormats = new[]
{
"Views/%1/{1}/Partials/{0}.cshtml",
"Views/%1/Shared/{0}.cshtml",
"Views/{1}/Partials/{0}.cshtml",
"Views/Shared/{0}.cshtml"
};
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return base.CreatePartialView(controllerContext, partialPath.ReplaceOrderType(CurrentOrderingMode()));
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
OrderType orderType = CurrentOrderingMode();
return base.CreateView(controllerContext, viewPath.ReplaceOrderType(orderType), masterPath.ReplaceOrderType(orderType));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
return base.FileExists(controllerContext, virtualPath.Replace("%1/",string.Empty));
}
private OrderType CurrentOrderingMode()
{
OrderType result;
_profileService = DependencyResolver.Current.GetService<IUserProfileService>();
if (_profileService == null || _profileService.OrderingType == 0)
{
IApplicationSettingService settingService =
DependencyResolver.Current.GetService<IApplicationSettingService>();
result =
settingService.GetApplicationSetting(ApplicationSettings.DefaultOrderingMode)
.ToEnumTypeOf<OrderType>();
}
else
{
result = _profileService.OrderingType;
}
return result;
}
}
Here is the StartUp class RazorGenerator uses to Register the ViewEngine.
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
The problem is:
This code is executed last (after I register the PosViewEngine) and it inserts the engine at the first position (meaning this is the engine that gets resolved 1st when serving up responses). This ends up finding a view - it is the core view.
If I change the code in the StartUp to Register my custom view engine first first and then the RazorGenerator engine
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Clear();
ViewEngines.Engines.Insert(0, new PosViewEngine());
ViewEngines.Engines.Insert(1, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
I end up with an exception on the FileExists(ControllerContext controllerContext, string virtualPath) method - "The relative virtual path 'Views/Account/LogOn.cshtml' is not allowed here."
It obviously has something to do with both physical and virtual paths being mixed together.
It looks like someone else was trying to do the same thing here but I didn't see an answer on this.
For anyone else wanting to try this approach I'll post the answer. Basically you need to implement a custom view engine that derives from the PrecompiledMvcEngine found in the RazorGenerator assembly.
public class PosPrecompileEngine : PrecompiledMvcEngine
{
private IUserProfileService _profileService;
public PosPrecompileEngine(Assembly assembly) : base(assembly)
{
LocatorConfig();
}
public PosPrecompileEngine(Assembly assembly, string baseVirtualPath) : base(assembly, baseVirtualPath)
{
LocatorConfig();
}
public PosPrecompileEngine(Assembly assembly, string baseVirtualPath, IViewPageActivator viewPageActivator) : base(assembly, baseVirtualPath, viewPageActivator)
{
LocatorConfig();
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return base.CreatePartialView(controllerContext, partialPath.ReplaceOrderType(CurrentOrderingMode()));
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
OrderType orderType = CurrentOrderingMode();
return base.CreateView(controllerContext, viewPath.ReplaceOrderType(orderType), masterPath.ReplaceOrderType(orderType));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
return base.FileExists(controllerContext, virtualPath.ReplaceOrderType(CurrentOrderingMode()));
}
}
In this class - I override the Locator Paths. Because I have the "base" compiled views in another assembly from the web application - we implemented a convention where the view engine will first look in a PosViews/{ordering mode}/{controller}/{view} path in the web application. If a view is not located -then it will look in the traditional /Views/controller/view. The trick here is the later is a virtual path located in another class library.
This allowed us to "override" an existing view for the application.
private void LocatorConfig()
{
//{0} = View Name
//{1} = ControllerName
//{2} = Area Name
AreaViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/{1}/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/Shared/(0}.cshtml",
//Next look in the POS Areas Shared
"PosAreas/{2}/Views/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core assembly
"Areas/{2}/Views/{1}/{0}.cshtml"
};
//Same format logic
AreaMasterLocationFormats = AreaViewLocationFormats;
AreaPartialViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/Partials/{PartialViewName}.cshtml
"PosAreas/{2}/Views/%1/{1}/Partials/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/Shared/(0}.cshtml",
//Next look in the hosting application shared folder
"PosAreas/{2}/Views/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core
"Areas/{2}/Views/{1}/{0}.cshtml"
};
ViewLocationFormats = new[]
{
"~/PosViews/%1/{1}/{0}.cshtml",
"~/PosViews/%1/Shared/{0}.cshtml",
"~/PosViews/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
MasterLocationFormats = ViewLocationFormats;
PartialViewLocationFormats = new[]
{
"~/PosViews/%1/{1}/{0}.cshtml",
"~/PosViews/%1/Shared/{0}.cshtml",
"~/PosViews/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
}
Register this engine in your application start up events.
public static void Configure()
{
var engine = new PosPrecompileEngine(typeof(ViewEngineConfig).Assembly)
{
UsePhysicalViewsIfNewer = true,
PreemptPhysicalFiles = true
};
ViewEngines.Engines.Add(engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
Here is the final key. When RazorGenerator gets installed view NuGet - you end up with this start-up class that will run on startup
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(Views.Core.RazorGeneratorMvcStart), "Start")]
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = true,
PreemptPhysicalFiles = true
};
ViewEngines.Engines.Add(engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
By default - RazorGenerator adds ViewEngine to the first in the collection
ViewEngines.Engines.Insert(0,engine);
You need to change that to an add
ViewEngines.Engines.Add(engine);
So it is added to engines last - this way your custom ViewEngine is used FIRST in locating views.
This approach allows you to reuse views in multiple applications while allowing a means to override that view.
This may be overkill for most applications - bust as I mentioned in the question - this is base product that we use to develop multiple client applications. Trying achieve reuse while maintaining a level of flexibility on a per client basis is something we were trying to achieve.

Setting the version number for .NET Core projects

What are the options for setting a project version with .NET Core / ASP.NET Core projects?
Found so far:
Set the version property in project.json. Source: DNX Overview, Working with DNX projects. This seems to set the AssemblyVersion, AssemblyFileVersion and AssemblyInformationalVersion unless overridden by an attribute (see next point).
Setting the AssemblyVersion, AssemblyFileVersion, AssemblyInformationalVersion attributes also seems to work and override the version property specified in project.json.
For example, including 'version':'4.1.1-*' in project.json and setting [assembly:AssemblyFileVersion("4.3.5.0")] in a .cs file will result in AssemblyVersion=4.1.1.0, AssemblyInformationalVersion=4.1.1.0 and AssemblyFileVersion=4.3.5.0
Is setting the version number via attributes, e.g. AssemblyFileVersion, still supported?
Have I missed something - are there other ways?
Context
The scenario I'm looking at is sharing a single version number between multiple related projects. Some of the projects are using .NET Core (project.json), others are using the full .NET Framework (.csproj). All are logically part of a single system and versioned together.
The strategy we used up until now is having a SharedAssemblyInfo.cs file at the root of our solution with the AssemblyVersion and AssemblyFileVersion attributes. The projects include a link to the file.
I'm looking for ways to achieve the same result with .NET Core projects, i.e. have a single file to modify.
You can create a Directory.Build.props file in the root/parent folder of your projects and set the version information there.
However, now you can add a new property to every project in one step by defining it in a single file called Directory.Build.props in the root folder that contains your source. When MSBuild runs, Microsoft.Common.props searches your directory structure for the Directory.Build.props file (and Microsoft.Common.targets looks for Directory.Build.targets). If it finds one, it imports the property. Directory.Build.props is a user-defined file that provides customizations to projects under a directory.
For example:
<Project>
<PropertyGroup>
<Version>0.0.0.0</Version>
<FileVersion>0.0.0.0</FileVersion>
<InformationalVersion>0.0.0.0.myversion</InformationalVersion>
</PropertyGroup>
</Project>
Another option for setting version info when calling build or publish is to use the undocumented /p option.
dotnet command internally passes these flags to MSBuild.
Example:
dotnet publish ./MyProject.csproj /p:Version="1.2.3" /p:InformationalVersion="1.2.3-qa"
See here for more information: https://github.com/dotnet/docs/issues/7568
Not sure if this helps, but you can set version suffixes at publish time. Our versions are usually datetime driven, so that developers don't have to remember to update them.
If your json has something like "1.0-*"
"dotnet publish --version-suffix 2016.01.02" will make it "1.0-2016.01.02".
It's important to stick to "semvar" standards, or else you'll get errors. Dotnet publish will tell you.
Why not just change the value in the project.json file. Using CakeBuild you could do something like this (optimizations probably possible)
Task("Bump").Does(() => {
var files = GetFiles(config.SrcDir + "**/project.json");
foreach(var file in files)
{
Information("Processing: {0}", file);
var path = file.ToString();
var trg = new StringBuilder();
var regExVersion = new System.Text.RegularExpressions.Regex("\"version\":(\\s)?\"0.0.0-\\*\",");
using (var src = System.IO.File.OpenRead(path))
{
using (var reader = new StreamReader(src))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if(line == null)
continue;
line = regExVersion.Replace(line, string.Format("\"version\": \"{0}\",", config.SemVer));
trg.AppendLine(line);
}
}
}
System.IO.File.WriteAllText(path, trg.ToString());
}
});
Then if you have e.g. a UnitTest project that takes a dependency on the project, use "*" for dependency resolution.
Also, do the bump before doing dotnet restore. My order is as follows:
Task("Default")
.IsDependentOn("InitOutDir")
.IsDependentOn("Bump")
.IsDependentOn("Restore")
.IsDependentOn("Build")
.IsDependentOn("UnitTest");
Task("CI")
.IsDependentOn("Default")
.IsDependentOn("Pack");
Link to full build script: https://github.com/danielwertheim/Ensure.That/blob/3a278f05d940d9994f0fde9266c6f2c41900a884/build.cake
The actual values, e.g. the version is coming from importing a separate build.config file, in the build script:
#load "./buildconfig.cake"
var config = BuildConfig.Create(Context, BuildSystem);
The config file looks like this (taken from https://github.com/danielwertheim/Ensure.That/blob/3a278f05d940d9994f0fde9266c6f2c41900a884/buildconfig.cake):
public class BuildConfig
{
private const string Version = "5.0.0";
public readonly string SrcDir = "./src/";
public readonly string OutDir = "./build/";
public string Target { get; private set; }
public string Branch { get; private set; }
public string SemVer { get; private set; }
public string BuildProfile { get; private set; }
public bool IsTeamCityBuild { get; private set; }
public static BuildConfig Create(
ICakeContext context,
BuildSystem buildSystem)
{
if (context == null)
throw new ArgumentNullException("context");
var target = context.Argument("target", "Default");
var branch = context.Argument("branch", string.Empty);
var branchIsRelease = branch.ToLower() == "release";
var buildRevision = context.Argument("buildrevision", "0");
return new BuildConfig
{
Target = target,
Branch = branch,
SemVer = Version + (branchIsRelease ? string.Empty : "-b" + buildRevision),
BuildProfile = context.Argument("configuration", "Release"),
IsTeamCityBuild = buildSystem.TeamCity.IsRunningOnTeamCity
};
}
}
If you still want to have the Solution Level SharedVersionInfo.cs you can do it by adding these lines to your project.json file:
"buildOptions": {
"compile": {
"includeFiles": [
"../../SharedVersionInfo.cs"
]
}
}
Your relative path may vary, of course.
use external version.txt file with version, and prebuild step to publish this version in projects