Access to the path is denied Xamarin forms - xamarin

I am trying to copy my db file to internal storage root/document. My codes are working good till Android 9 but after it i am getting the error "System.UnauthorizedAccessException: Access to the path "/storage/emulated/0/Documents/FruitsApp/Backup/Fruits.db_2021-06-28 12:20:20" is denied"
I have try lots of way to copy all are working before Android 9 but after it i am getting above error. I am sharing my all codes. Thanks in advance.
----copy code
Java.IO.File mediaStorageDir = new Java.IO.File(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments) + path);
if (!mediaStorageDir.Exists())
{
var tt = mediaStorageDir.Mkdirs();
if (mediaStorageDir.Mkdirs() == false)
{
var fail = "failed to create";
}
}
var directoryPath = mediaStorageDir.AbsolutePath;
////////--this way to create folder is working till andorid 9
//var PathExists = Directory.Exists(directoryPath);
//if (PathExists ==false)
//{
// Directory.CreateDirectory(directoryPath);
//}
var dbPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "Fruits.db");
FileInfo file = new FileInfo(dbPath);
var dbName = file.Name;
var fullFileName = string.Concat("/", dbName + "_", Global.CurrentDateTime());
var newpath = string.Format("{0}{1}", directoryPath, fullFileName);
//////--- First way copy file from source to destination is working tille android 9
//using (FileStream sourceStream = System.IO.File.Open(dbPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
//using (FileStream destinationStream = System.IO.File.Create(newpath))
//{
// sourceStream.CopyToAsync(destinationStream);
//}
//////--- 2nd way copy file from source to destination is working tille android 9
byte[] dbFile = System.IO.File.ReadAllBytes(dbPath);
System.IO.File.WriteAllBytes(newpath, dbFile);
//////--- 3rd way copy file from source to destination is working tille android 9
//file.CopyTo(newpath);
I have try 3 ways to copy file from source to another all ways are working till android 9 but not working after android 9.
--Android Manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1"
android:versionName="1.0" package="com.NewWave.FruitApp" android:installLocation="auto">
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="30" />
<application android:requestLegacyExternalStorage="true" android:label="FruitApp"
android:theme="#style/MainTheme"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />-->
</manifest>

I have a SQLite database saved in the internal storage of my cellphone, in a folder created folder named SQLite. I had troubles with the permissions and the path. Here's how I solved it:
private readonly SQLiteAsyncConnection _conn;
public LocalContext()
{
// Create directory for SQLite
Directory.CreateDirectory(Path.Combine("/storage/emulated/0/","SQLite"));
string dbPath = Path.Combine(
"/storage/emulated/0/SQLite",
"ventaMovil.db");
_conn = new SQLiteAsyncConnection(dbPath);
}
Before I called my LocalContext I check if you have writing permissions:
private async Task<bool> CheckPermissions()
{
var status = await _permissionService.CheckAndRequestPermissionAsync(new Permissions.StorageWrite());
if (status != PermissionStatus.Granted)
{
await App.Current.MainPage.DisplayAlert("Error", "You do not have writing permissions", "Okay");
return false;
}
return true;
}
For the permissions I use Xamarin Essentials:
public class PermissionService : IPermissionService
{
public async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>(T permission) where T : Permissions.BasePermission
{
var status = await permission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await permission.RequestAsync();
}
return status;
}
}
And in the Android Manifest I have these two:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Related

Xamarin Essentials Permissions failing to grant permission

I'm currently writing a Xamarin Forms app which requires use of the camera, in the code below I am requesting the permission using the Xamarin Essentials Permissions which comes back as "Granted"; immediately following that I am requesting use of the camera to take a photo, which throws the following error.
ex = {Plugin.Media.Abstractions.MediaPermissionException: Camera permission(s) are required.
The permission code
public static async Task<bool> GetPermission<TPermission>() where TPermission : BasePermission, new()
{
var hasPermission = await Permissions.CheckStatusAsync<TPermission>();
if (hasPermission == PermissionStatus.Granted)
return true;
else if (hasPermission == PermissionStatus.Disabled)
return false;
var result = await Permissions.RequestAsync<TPermission>();
if (result != PermissionStatus.Granted)
return false;
return true;
}
The photo manager code
if(!await PermissionHelpers.GetPermission<Permissions.Camera>())
{
await new ErrorAlert().Show("App can't take a picture without permission to use the camera");
return string.Empty;
}
var photo = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
{
PhotoSize = PhotoSize.Small,
SaveToAlbum = false
});
As previously said, the GetPermission method returns true, but still the error is thrown.
I'm currently running this on Android.
My AndroidManifest.xml has these permission in it.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
I have now made a sample project to showcase my issue
GitHub Repo for the issue
don't forget
Android
protected override void OnCreate(Bundle savedInstanceState) {
//...
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState); // add this line to your code, it may also be called: bundle
//...
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
Documentation
First of all, I notice you use Xam.Plugin.Media, this plugin need WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE and android.permission.CAMERA in Android, You should request these permission at runtime.
You can use following code in the MainActivity
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) != (int)Permission.Granted)
{
RequestPermissions(new string[] { Manifest.Permission.Camera, Manifest.Permission.WriteExternalStorage, Manifest.Permission.ReadExternalStorage }, 0);
}
}
Here is running gif.
Update
If you use this CrossMedia, you need grant Storage and Camera permission.Please open your PhotoManager.cs Add the request storage code like following code.
public class PhotoManager
{
public async Task<string> TakeNewPhoto()
{
try
{
if (!CrossMedia.IsSupported || !CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
return string.Empty;
}
if (!await PermissionHelpers.GetPermission<Permissions.Camera>())
{
return string.Empty;
}
//=====================================add above line==================================================
if (!await PermissionHelpers.GetPermission<Permissions.StorageWrite>())
{
return string.Empty;
}
var photo = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
{
PhotoSize = PhotoSize.Small,
SaveToAlbum = false
});
if (photo != null)
{
return "photo taken successfully";
}
return string.Empty;
}
catch (Exception ex)
{
return ex.Message;
}
}
}
Here is your issueProjects' running GIF.
thank you for all of your time gone into helping me to resolve this issue.
It turned out that if you are using Xamarin essentials version 1.5.0 you need to install the CurrentActivity NuGet plugin to your android project.
Or, a better solution is update to 1.5.1 which resolves the issue entirely.
You must add permissions for camera to your Android Manifest file!
In Visual Studio right click your android project.
Go to Options -> Build -> Android Application and tick the box in required permissions that says camera.
NB: If you are going to be recording you may also want to enable microphone and audio permissions.
You must also add:
<uses-feature android:name="android.hardware.camera" />
To your android manifest.

Having problems saving camera images

I have been through the contents and cant seem to find an answer to my issue.
I have an imagefile in my app, and a button... I am following the tutorial on android developers https://developer.android.com/training/camera/photobasics#java but am having problems saving the file. At one point, I was able to save to cache, but i would rather save to a folder in pictures.
Here is my code...
static final int REQUEST_IMAGE_CAPTURE = 1;
private void dispatchTakePictureIntent() {
Intent dispatchTakePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (dispatchTakePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
photoFile = createImageFile();
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.ConfinedSpaceManagement",
photoFile);
dispatchTakePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(dispatchTakePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
imageView.setImageBitmap(imageBitmap);
}
}
String currentPhotoPath;
private File createImageFile(){
// Create an image file name
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!storageDir.exists()) {
if (!storageDir.mkdirs()) {
Log.d("Confined_Space", "failed to create Directory");
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
return new File(storageDir.getPath() + File.separator + "ConSp" + timeStamp + ".jpg");
}
//String imageFileName = "JPEG_" + timeStamp + "_";
////////////////////////////////////////////////////////////////
static final int REQUEST_TAKE_PHOTO = 1;
}
and this is my manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.confinedspacemanagement">
<uses-feature android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<meta-data
android:name="com.google.android.actions"
android:resource="#xml/file_paths" />
<activity android:name=".Signature" />
<activity android:name=".AuditWork" />
<activity android:name=".DisplayIndividual" />
<activity android:name=".SecondActivity" />
<activity android:name=".Definition" />
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.confinedspacemanagement.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
</application>
</manifest>
and here is my xml.file_path
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>
The activity is a sqlite form page. I am trying to take a picture, save it to a specified file, and then save the file path into the database.
Any help out there??? Should I be using camera2???
I found an answer at https://www.youtube.com/watch?v=VqgZxiU2knM by Mr. PRABEESH R K.
The image is saving fine, but I am taking the picture from an activity that is not the main activity. When I return the result, the page goes back to main activity, not the page the camera was originally requested from. Is there any way to keep it on the sending page?

not getting how to use incoming image from android chooser intent

I'm trying to display the image in main page. the image will come from whats app profile upon sharing that image to my app.
app.js
if (intent.getClipData()) {
let imageUri = intent.getClipData().getItemAt(0).getUri()
console.log(imageUri);
// content://com.whatsapp.fileprovider/external/WhatsApp/.Shared/photo.jpg
global.imageUri = imageUri;
}
xml
<StackLayout class="p-20">
<Image src="{{ imageUri }}" stretch="none" />
</StackLayout>
viewModel
imageUri: global.imageUri,
getting this error
Error in downloadBitmap - java.net.MalformedURLException: Unknown protocol: content
i need to know how to use incoming images from intent services of android.
add these things in android manifest
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
to handle it copy paste this code anywhere you like
if (platform_1.isAndroid) {
application.android.on(application.AndroidApplication.activityCreatedEvent, function (args) {
var activity = args.activity;
// Get intent, action and MIME type
var intent = activity.getIntent();
var action = intent.getAction();
var type = intent.getType();
if (android.content.Intent.ACTION_SEND === action && type != null) {
if (type.startsWith("text/")) {
handleSendText(intent); // Handle text being sent
}
else if (type.startsWith("image/")) {
handleSendImage(intent); // Handle single image being sent
}
}
else if (android.content.Intent.ACTION_SEND_MULTIPLE === action && type != null) {
if (type.startsWith("image/")) {
handleSendMultipleImages(intent); // Handle multiple images being sent
}
}
else {
// Handle other intents, such as being started from the home screen
}
});
}
function handleSendText(intent) {
if (platform_1.isAndroid) {
var sharedText = intent.getStringExtra(android.content.Intent.EXTRA_TEXT);
if (sharedText != null) {
// Update UI to reflect text being shared
console.log("sharedText: ", sharedText);
console.log("Text received!");
vm.set("sharedText", sharedText);
}
}
}
function handleSendImage(intent) {
if (platform_1.isAndroid) {
var imageUri = intent.getParcelableExtra(android.content.Intent.EXTRA_STREAM);
if (imageUri != null) {
// Update UI to reflect image being shared
console.log("Image received!");
var appContext = application.android.context;
var bitmap = android.provider.MediaStore.Images.Media.getBitmap(appContext.getContentResolver(), imageUri);
console.log("bitmap: ", bitmap);
vm.set("bitmap", bitmap);
}
}
}
function handleSendMultipleImages(intent) {
if (platform_1.isAndroid) {
var imageUris = intent.getParcelableArrayListExtra(android.content.Intent.EXTRA_STREAM);
if (imageUris != null) {
// Update UI to reflect multiple images being shared
console.log("imageUris: ", imageUris);
console.log("Multiple images received!");
}
}
}
You can not directly display image using content uri. You will have to parse the actual file url using MediaStore
You may refer the source code of nativescript-imagepicker for working sample. It returns the actual file path after parsing the content uri when you a image is selected from gallery.

Android Camera force close after capturing on KitKat

Currently I am creating apps that allowed user to take photo and show it to an ImageView. It works like charm on Android 5.1.1 Sony M2 Dual.
But on Kitkat 4.4.2 Samsung Galaxy Tab 3 and KitKat 4.4.4 Xiaomi Redmi 2, the camera force close after capturing image.
I don't know if this will be useful or not, but I realized that the camera probably force close because after capturing, on those two KitKat device, user will be prompted to accept the captured picture or not, then if accepted, it will be back to my current Activity.
Beacuse on my Sony with 5.1.1, user will not be prompted for the captured picture, it will be straight back to my current Activity.
Here I included the corresponding code.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_job_order_line_form);
bAddPhoto = (TextView) findViewById(R.id.bAddPhoto);
bSaveJOLine = (TextView) findViewById(R.id.bSaveJOLine);
editDescription = (EditText) findViewById(R.id.editDescription);
editQty = (EditText) findViewById(R.id.editQty);
editPrice = (EditText) findViewById(R.id.editPrice);
ivImage = (ImageView) findViewById(R.id.imageTaken);
Intent intent = getIntent();
jobId = intent.getIntExtra("jobId", 0);
docNo = intent.getStringExtra("docNo");
bAddPhoto.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intentCamera.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
Toast.makeText(OJobOrderLineFormActivity.this, "Create file failed!",
Toast.LENGTH_SHORT).show();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(OJobOrderLineFormActivity.this,
"com.opentoko.opentokolaundry.fileprovider",
photoFile);
System.out.println(photoURI);
intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(intentCamera, 360);
}
}
}
});
bSaveJOLine.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Snackbar.make(v, "Item has been saved!", Snackbar.LENGTH_LONG)
.setAction("OK", null).show();
}
});
}
Create temporary file function :
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmm").format(new Date());
String imageFileName = docNo + "_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
System.out.println(storageDir);
System.out.println(image);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
System.out.println(currentPhotoPath);
return image;
}
onActivityResult :
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 360:
if(resultCode == RESULT_OK) {
int targetW = ivImage.getWidth();
int targetH = ivImage.getHeight();
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
ivImage.setImageBitmap(bitmap);
}
}
}
I can't really figured out what causing this because in logcat there is seems no error at all, my build still running like usual.
Here is my logcat after entering current activity for take the picture:
D/TextLayoutCache: Enable myanmar Zawgyi converter
D/TextLayoutCache: Enable myanmar Zawgyi converter
D/TextLayoutCache: Enable myanmar Zawgyi converter
D/TextLayoutCache: Enable myanmar Zawgyi converter
I/System.out: /storage/emulated/0/Android/data/com.opentoko.opentokolaundry/files/Pictures
I/System.out: /storage/emulated/0/Android/data/com.opentoko.opentokolaundry/files/Pictures/A-00003_20170127_1758_-1345208956.jpg
I/System.out: /storage/emulated/0/Android/data/com.opentoko.opentokolaundry/files/Pictures/A-00003_20170127_1758_-1345208956.jpg
I/System.out: content://com.opentoko.opentokolaundry.fileprovider/my_images/A-00003_20170127_1758_-1345208956.jpg
W/IInputConnectionWrapper: showStatusIcon on inactive InputConnection
This logcat already in this exact point when it start begin open the camera. After capturing, nothing added to the logcat.
This is part of my Android-Manifest.xml :
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.opentoko.opentokolaundry.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
Does anyone here had or has same problem with me? Any solution? Any help will be appreciated.
I've found solution for intent camera force close on KitKat by trial and error.
Seems I don't need FileProvider at all.
I change storageDir in my createImageFile() to this :
File storageDir = getExternalFilesDir("Pictures");
And photoURI to this :
Uri photoURI = Uri.fromFile(photoFile);
Now I have the right fullscreen image to display on KitKat too.
And I remove provider from my manifest. Now I have my captured image file at Android/data/my.package.name/files/Pictures/ folder.

dotless.comple.exe. Why does it stop? what im i doing wrong? Or suggest me and alternative approach

Im trying to compile my less file to css, im using the dotless
I installed it with the nuget. This is the settings I got setup in project "Pre-build event command line"
“$(SolutionDir)packages\dotless.1.3.1.0\tool\dotless.compiler.exe”
“$(ProjectDir)Content\style.less”
“$(ProjectDir)Content\style_less.css”
What happends on build is that it just opens the style.less file in notepad and just stops there. When i close the less file, it opens the output css (empty) and same story here it just stops, when i close the css it does the regular build.
Ideas? is it supose to take time? is this behavior normal?
EDIT
Is there any other approach to compile LESS in Windows environment?
As I said in some question comment, because DotLess didn't work for me, I switched to other approach: compiling LESS with official LESS compiler (JavaScript one) and executed using Windows Script Host.
Create a folder inside your solution directory (i.e. where your .sln and project directories are stored for your particular solution). Call your folder as Build.
You need these scripts (create a file for each one inside Build folder, I'm giving you the whole file name):
lessc.wsf
<!--
Less.js compiler for Windows Script Host
http://blog.dotsmart.net/
Copyright (c) 2010, Duncan Smart
Licensed under the Apache 2.0 License.
-->
<job>
<script language="jscript">
// Stub out globals
var window = this;
var location = window.location = {
port: 0,
href: ''
};
var fso = new ActiveXObject("Scripting.FileSystemObject");
var input = null;
var util = {
readText: function (filename) {
//WScript.StdErr.WriteLine("readText: " + filename);
var file = fso.OpenTextFile(filename);
// Don't error on empty files
var text = file.AtEndOfStream ? '' : file.ReadAll();
// Strip off any UTF-8 BOM
var utf8bom = String.fromCharCode(0xEF, 0xBB, 0xBF);
if (text.substr(0, utf8bom.length) == utf8bom) {
text = text.substr(utf8bom.length);
}
file.Close();
return text;
}
};
// XMLHttpRequest that just gets local files. Used when processing "#import"
function XMLHttpRequest(){}
XMLHttpRequest.prototype = {
open: function (method, url, async) {
this.url = url;
},
send: function () {
// get the file path relative to the input less file/directory
var currDir = fso.folderExists(input) ? input : fso.getParentFolderName(input);
var filename = fso.BuildPath(currDir, this.url);
//WScript.StdErr.WriteLine("XHR.send " + filename);
// Little hack so *.less will resolve to *.less.css also. Helps with Visual Studio
// ensuring that file BuildAction is set to Content and you get rudimentary syntax highlighting with no set up.
if (filename.match(/.less$/i) && !fso.FileExists(filename)) {
filename = filename.replace(/.less$/i, '.less.css');
}
try {
this.status = 200;
this.responseText = util.readText(filename);
}
catch (e) {
this.status = 404;
this.responseText = e.description;
}
},
setRequestHeader: function () {},
getResponseHeader: function () {}
};
// Fake document
var document = {
_dummyElement: {
childNodes: [],
appendChild: function(){},
style: {}
},
getElementsByTagName: function(){ return []; },
getElementById: function(){ return this._dummyElement; },
createElement: function(){ return this._dummyElement; },
createTextNode: function(){ return this._dummyElement; }
};
</script>
<!-- less.js from https://github.com/cloudhead/less.js/tree/master/dist/ -->
<script language="jscript" src="less.js" />
<script language="jscript">
// Parse args
var args = {};
for (var i = 0; i < WScript.Arguments.Length; i++) {
var arg = WScript.Arguments.Item(i);
// Handle "-switch" and "--switch"
var match = arg.match(/^--?([a-z][0-9a-z-]*)$/i);
if (match) {
i = match[1];
arg = true;
}
args[i] = arg;
}
input = args[0];
var output = args[1];
if (fso.folderExists(input)) {
input = fso.getAbsolutePathName(input);
var files = getFiles(input, /\.less$/i);
for (var i = 0; i < files.length; i++) {
var file = files[i];
convert(file.path, output + '\\' + file.name.replace( /\.less$/i, '.css'));
}
}
else {
if (fso.folderexists(output)) {
output = fso.getAbsolutePathName(output) + '\\' + fso.getfile(input).name.replace(/\.less$/i, '.css');
}
convert(input, output);
}
// Returns array of {name:'foo.bar', path:'c:\baz\foo.bar'} for given directory and pattern
function getFiles(dir, regex) {
var e = new Enumerator(fso.getFolder(dir).files);
var files = []
for (; !e.atEnd(); e.moveNext()) {
if (regex.test(e.item().path)) {
files.push({
name: e.item().name,
path: e.item().path
});
}
}
return files;
}
function convert(input, output) {
if (!input) {
WScript.StdErr.WriteLine("lessc.wsf: no input files");
WScript.StdErr.WriteLine("Usage:");
WScript.StdErr.WriteLine(" Single file: cscript //nologo lessc.wsf input.less [output.css] [-compress]");
WScript.StdErr.WriteLine(" Directory: cscript //nologo lessc.wsf inputdir outputdir [-compress]");
WScript.Quit(1);
}
var data;
if (input == '-') {
var chunks = [];
while (!WScript.StdIn.AtEndOfStream)
chunks.push(WScript.StdIn.ReadAll());
data = chunks.join('');
}
else {
data = util.readText(input);
}
var parser = new less.Parser({
filename: input
});
try {
parser.parse(data, function (err, tree) {
if (err) {
WScript.StdErr.WriteLine("ERR: ");
for (var i in err) {
if (err[i]) {
WScript.StdErr.WriteLine(" " + i + ': ' + err[i]);
}
}
WScript.Quit(2);
}
else {
var css = tree.toCSS({
compress: args.compress
});
if (output) {
if(fso.FileExists(output))
{
var checkfile = fso.GetFile(output);
if(checkfile.Attributes & 1)
{
checkfile.Attributes = checkfile.Attributes ^ 1
}
}
var outputfile = fso.CreateTextFile(output);
outputfile.Write(css);
outputfile.Close();
}
else {
WScript.StdOut.Write(css);
}
}
});
}
catch (e) {
WScript.StdErr.WriteLine("ERROR:");
for (var i in e) {
if (e[i]) {
WScript.StdErr.WriteLine(" " + i + ': ' + e[i]);
}
}
WScript.Quit(3);
}
// Sometimes less will return errors inside the fake HTML element
if (document._dummyElement.innerHTML && document._dummyElement.innerHTML.match(/Syntax Error/i)) {
var s = document._dummyElement.innerHTML;
s = s.replace(/<[^>]+(\/?)>/g, function (m) { return m.indexOf('/') > 0 && m !== '</label>' ? "\n" : '' });
s = s.replace(/\n+/g, '\n');
WScript.StdErr.WriteLine("ERR: ");
WScript.StdErr.WriteLine(s);
WScript.Quit(2);
}
}
</script>
</job>
lessc.cmd
::For convenience
#cscript //nologo "%~dp0lessc.wsf" %*
less.targets
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DotLessCompilerPath>$(SolutionDir)Build\lessc.cmd</DotLessCompilerPath>
</PropertyGroup>
<Target Name="AllLessToCss" AfterTargets="AfterBuild">
<CreateItem Include="$(ProjectDir)**\*.less">
<Output TaskParameter="Include" ItemName="LessFile" />
</CreateItem>
<Message Text="" Importance="high" />
<Message Text="Now compiling LESS files..." Importance="high" />
<Message Text="------------------------------------------------------------" Importance="high" />
<Message Text="" Importance="high" />
<Message Text="File: "%(LessFile.FullPath) ======>>>>> "%(LessFile.RootDir)%(LessFile.Directory)%(LessFile.FileName).css"" Importance="high" />
<Exec Command=""$(DotLessCompilerPath)" "%(LessFile.FullPath)" "%(LessFile.RootDir)%(LessFile.Directory)%(LessFile.FileName).css" -compress" />
<Message Text="" Importance="high" />
<Message Text="------------------------------------------------------------" Importance="high" />
<Message Text="" Importance="high" />
</Target>
</project>
Once you've created the whole files inside Build folder, you need to modify your ASP.NET application project file (i.e. .csproj). In order to modify project file, you need to unload your project in Visual Studio IDE (right-click on project node in your Solution Explorer and choose "Unload Project").
Now right-click again on unloaded project and choose "Edit project file". Go at the end of project file and just before </project> add the following XML code:
<Import Project="$(SolutionDir)Build\less.targets" />
Finally, reload your ASP.NET project in Visual Studio, build it and, if everything went fine, you'll find a ".css" counterpart for any of your ASP.NET project LESS (.less) files!
I installed Less.net manually in my project and this is in my Pre-Build event:
If "$(ConfigurationName)" == Debug (
FOR %%i IN ("$(ProjectDir)css\*.less") DO ("$(ProjectDir)Tools\dotless.Compiler.exe" "%%i" )
) Else (
FOR %%i IN ("$(ProjectDir)css\*.less") DO ("$(ProjectDir)Tools\dotless.Compiler.exe" -m "%%i" )
)
This code will look in the css folder and transform all .less files to css files, when the project is in debug mode the css will be readable otherwise it's compressed. Don't forget to change the paths so it works in your environment. If there is an error in your .less file the error and line number will be shown in the Output window.

Resources