Improving dotnet publish by writing a killer bash script

What’s the difference between writing a hundred identical emails by hand and sending them vs writing an email once and having some automated process send it out a thousand times?

It’s about roughly the same as the difference between publishing a dotnet app manually versus publishing it through a bash script.


The project I’m working on temporarily requires a manual deployment of multiple dotnet applications. The old devs who transferred this project to me, showed me how they were doing this and I was astounded. They were navigating through the UI of Visual Studio, changing setting and pressing buttons in menus I’ve never seen before for everything (running, cleaning, building, publishing). For context: I use vscode on a Macbook… No way this was going to work!


There is a really useful extension called vscode-solution-explorer which comes close enough in letting you do some Visual Studio-esque things, but it was clear as day that the dotnet CLI was going to be the main interface for this project from now on.


The star of the show for manual deployment of the compiled applications is definitely the dotnet publish command.


Okay sounds very easy and straight forward. Nothing special to it right?


Here we go: dotnet clean MyProject and dotnet build MyProject aaand dotnet publish MyProject -c Release





Cool! A quick dotnet MyProject.dll to see if it works- aaaand it tells me that one specific OldUnusedThing.dll is missing, something weird is going on in MyProject.deps.json, and the world is ending! Worst part is that this only happens in the server and not locally… “But I ran dotnet remove OldUnusedThing and my entire codebase including .csproject files are totally void of any mention of OldUnusedThing …”




Yeah, sorry… Not good enough!





Okay. If dotnet cleanis not going to actually clean my project thoroughly, I will do it myself. First I clean my nuget cache dotnet nuget locals all — clear , then I delete the bin folder, the obj folder, and even the .vs folder which got generated when I played around with Visual Studio for Mac…


Awesome! That fixed the problem-… for only ONE of our microservices! Also you were accidentally on the wrong git branch. You were meant to deploy Development, not fix-2425-attempt-2.


Automation to the rescue! All your configuration, version control, cleanup, and setup should be consistent on every build. When you fix something in your build process, it should be fixed whenever you repeat the process in the future as well.


If you agree with this, and you have to build a repo with multiple dotnet micro services from your local device a lot, you might find my bash script useful.


The bash script assumes your repo has the following architecture

MyProjectRepo/
-publish.sh
-MyProjectRepo.sln

-bin/
-obj/
-.vs/

-Service1/
--bin/
--obj/
--Service1.csproject

-Service2/
--bin/
--obj/
--Service2.csproject

-Service3/
--bin/
--obj/
--Service3.csproject



You want the following to happen:

  • All my micro services should get published
  • I should not get bugs in my deployment environment whenever I remove a package
  • I want my project to still work locally and I don’t want to break my IDE’s Omnisharp Intellisense
  • All my micro service builds should be cozy together in a nice little publishes/ folder
  • If some other device (other dev’s machine, or a server) runs my script, it should just work
  • I want to avoid publishing and deploying the wrong branch on accident and having to undo and then redo all my hard work



Epic! Here we go:

# This is a tool to publish all services to a /publishes folder
my_project="MyProjectRepo";

# ======== USER PROMPTING ============ #

# select environment
echo "🌍 Environment (dev|production):";
read env;
echo "🌍 Selected environment: $env";

# auto pre-select target branch
if test "$env" = "dev";
then
  target_branch="Development";
elif ["$env" == "production"];
then
  target_branch="master";
else
  exit 1;
fi

# Select target branch
read -p "🌳 Automatically switch to appropriate git branch instead of using current branch? (y|n):" -n 1 -r;



# ======== GIT STUFF ============ #
stash_name="bash_auto_stash on $(date)";

# this does stuff if the user said yes
store_git_changes() {
  original_branch=$(git symbolic-ref --short HEAD);
  if [[ $REPLY =~ ^[Yy]$ ]];
  then
    git_before;
  fi
}

git_before() {
  if git diff --quiet; then
    git_changes="0";
  else
    echo "⚠️ Don't touch your files while this bash script is in progress ⚠️";
    echo "⚠️ If this script gets canceled in any way, manually check your current git branch and stash ⚠️";
    git_changes="1";
    echo "📁📁📁 STASHING CHANGES AS: $original_branch - $stash_name 📁📁📁";
    git stash push -m "$stash_name";
  fi
  echo "🌳🌳🌳 CHECKING OUT: $target_branch 🌳🌳🌳";
  git checkout $target_branch;
  git pull;
}

git_after() {
  echo "🌳🌳🌳 CHECKING OUT: $original_branch 🌳🌳🌳";
  git checkout $original_branch;
  if test "$git_changes" = "1"; then
    echo "📂📂📂 REAPPLYING STASH 📂📂📂";
    git stash pop 0;
  fi
}

# ======== FILE CLEANUP ============ #

# Preparation
echo "📤 Publishing all micro services to ";
pwd;
echo "/publishes/";
echo "🧹 Cleaning out publishes folder";
rm -R publishes;


# ======== DOTNET STUFF ============ #

build_dotnet() {
  echo "ℹ️ path: $1";
  echo "📤 Attempting to publish following API 📤";
  cd "./$1";
  pwd;
  dotnet publish -c Release  -o ../publishes/$1 ;
  cd "../" ;
}

store_git_changes;

echo "🧹 Clearing nuget cache";
dotnet nuget locals all --clear

echo "🧹 Cleaning out repo's folders";
rm -R "./.vs";
rm -R "./obj";
rm -R "./bin";

# Recursively remove all those folders
echo "🧹 Cleaning out obj and bin folders in micro services";
find . -iname "bin" -o -iname "obj" -print0 | xargs -0 rm -rf

# building out my entire solution file is mainly for my IDE to not give me issues
# you could skip this if this is running on a server
echo "🚧🧹 Clean & Build 🧹🚧";
dotnet clean "./$(my_project).sln"
dotnet build "./$(my_project).sln"

echo "📤 Attempting to publish Solution 📤";
dotnet publish "./$(my_project).sln" -c Release;

# publish those services!
build_dotnet "Service1"
build_dotnet "Service2"
build_dotnet "Service3"

git_after;

echo "✅✅✅We did it✅✅✅";



And there we go! Now I can finally just simply remove deprecated dependencies without worrying about how the server might judge me! No worries about what branch I’m currently in, whether another dev’s build process is the same as mine, or whether we configure or commands in the same way! I am by no means a super expert on the dotnet CLI or even git. But it won’t get in my way of publishing a killer build of my dotnet app, because every new dotnet cli trick I learn get permanently added to every build thanks to choosing bash over manual labor.


In my project I even extended this script to include my React frontend build process. I know that if there ever is any problem with deployment, it is at least never me skipping a dotnet nuget locals all --clear or still calling the recently renamed SubscriptionAPI by its old name PaymentsAPI and wondering why it doesn’t work.


At least automating your build process beats trusting a team of devs to know how to use either Visual Studio or dotnet CLI properly, or being dependent on the one lead dev who knows what he’s doing for every build. PLUS now your build process is subject to the same benefits of version control as the rest of your code base!

logo animation