DOTNET EXPERT BLOG

Vue Tutorial for Beginners: Build Your First Vue App with C# and ASP.NET Core

9/29/2026 10:10:03 PM Noor All Safaet Loading... 0

Vue is a practical frontend framework for developers who want to build modern, interactive web applications with a clean component-based approach.

For C# developers, Vue fits naturally into a full stack architecture where Vue handles the browser interface while ASP.NET Core handles APIs, validation, business logic, authentication, and database operations.

A typical application can look like this:

Vue
   ↓
HTTP / JSON
   ↓
ASP.NET Core Web API
   ↓
C# Application Logic
   ↓
Entity Framework Core
   ↓
SQL Server / PostgreSQL

In this Vue tutorial for beginners, we will build the foundation of a Product Management application instead of learning Vue through disconnected examples.

You will learn how to:

  • Create a Vue application

  • Build Single-File Components

  • Understand the Composition API

  • Work with ref() and reactive()

  • Use Vue directives

  • Handle events

  • Create computed values

  • Pass data with props

  • Send events with emits

  • Build forms with v-model

  • Connect Vue to an ASP.NET Core Web API

  • Organize HTTP requests in a service

  • Handle loading and API errors

  • Configure CORS

  • Add Vue Router

  • Read route parameters

  • Create, read, update, and delete products

  • Connect ASP.NET Core to Entity Framework Core

The goal is not to memorize every Vue feature.

The goal is to understand how Vue works well enough to start building a real application.


What Is Vue?

Vue is a JavaScript framework for building user interfaces and web applications.

Vue applications are built from components.

Instead of putting the entire interface into one large page, you can divide it into smaller reusable pieces such as:

AppHeader
NavigationMenu
ProductList
ProductCard
ProductForm
CustomerList
Dashboard
SalesReport

Each component can manage a specific responsibility.

Vue is commonly used to build:

  • Business applications

  • Dashboards

  • SaaS products

  • Customer portals

  • Administrative systems

  • Inventory applications

  • E-commerce interfaces

  • Interactive websites

Vue templates remain close to familiar HTML while providing features for reactive state, events, conditions, loops, forms, and component communication.


Vue and C#: How Do They Work Together?

Vue and C# normally run in different parts of the application.

Vue handles the frontend:

Browser
   ↓
Vue

C# and ASP.NET Core handle the backend:

ASP.NET Core
      ↓
Application Logic
      ↓
Entity Framework Core
      ↓
Database

They communicate through HTTP APIs:

Browser
   ↓
Vue Application
   ↓
HTTP Request
   ↓
ASP.NET Core API
   ↓
C# Business Logic
   ↓
Database
   ↓
JSON Response
   ↓
Vue Application

Vue should not connect directly to SQL Server or PostgreSQL.

The backend should control database access, validation, authorization, and business rules.


What Should You Know Before Learning Vue?

Vue is approachable, but learning a few fundamentals first will make the framework much easier to understand.

HTML

You should understand basic elements such as:

<h1>Products</h1>

<input type="text">

<button>Save</button>

CSS

You should know the basics of:

  • Selectors

  • Classes

  • Layout

  • Spacing

  • Responsive design

JavaScript

Before going deeply into Vue, understand:

  • Variables

  • Functions

  • Objects

  • Arrays

  • Array methods

  • ES modules

  • Destructuring

  • Promises

  • Async and await

TypeScript

Vue works with both JavaScript and TypeScript.

We will use TypeScript because explicit types are useful when working with models and API contracts.

C# developers may also find many TypeScript concepts familiar.


Creating Your First Vue Application

Create a Vue project:

npm create vue@latest

Choose TypeScript when prompted if you want to follow the examples in this tutorial.

Move into the project directory:

cd vue-product-app

Install the packages:

npm install

Run the development server:

npm run dev

The terminal displays the local address where your Vue application is running.


Understanding Vue Single-File Components

Vue commonly organizes components in .vue files called Single-File Components, or SFCs.

A component can contain:

Template
Script
Style

Create:

ProductTitle.vue

Then:

<script setup lang="ts">
const title = 'Product Management'
</script>

<template>
  <div>
    <h2>{{ title }}</h2>
    <p>Manage your products from this page.</p>
  </div>
</template>

The <script setup> block contains component logic.

The <template> block contains the UI.

You can import the component elsewhere:

<script setup lang="ts">
import ProductTitle from './components/ProductTitle.vue'
</script>

<template>
  <ProductTitle />
</template>

This component-based model is one of the foundations of Vue development.


Understanding Vue Template Syntax

Vue templates can display JavaScript and TypeScript values.

For example:

<script setup lang="ts">
const productName = 'Laptop'
</script>

<template>
  <h2>{{ productName }}</h2>
</template>

Vue renders:

Laptop

The double curly braces are used for text interpolation.

You can also use simple expressions:

<p>{{ productName.toUpperCase() }}</p>

Avoid placing complicated business logic directly inside templates.

When logic becomes more complex, move it into the script section or a reusable function.


Understanding Reactive State with ref()

Vue interfaces need to react when application data changes.

The Composition API provides ref() for reactive values.

For example:

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <p>Current count: {{ count }}</p>

  <button @click="increment">
    Increase
  </button>
</template>

Inside TypeScript:

count.value

is used to access the value.

Inside a Vue template, refs are conveniently unwrapped:

{{ count }}

When count changes, Vue updates the relevant part of the interface.


ref() vs reactive()

Vue also provides reactive().

For example:

import { reactive } from 'vue'

const product = reactive({
  name: '',
  price: 0,
  stockQuantity: 0
})

You can then update properties directly:

product.name = 'Laptop'
product.price = 75000

With ref(), an object might instead look like:

const product = ref({
  name: '',
  price: 0,
  stockQuantity: 0
})

and script-side access uses:

product.value.name = 'Laptop'

A simple beginner guideline is:

  • ref() works well for primitive values and is also perfectly valid for objects.

  • reactive() can be convenient when you want a reactive object whose properties are accessed directly.

You do not need to force every value into one approach.

Understanding both is more useful than treating one as universally better.


Handling Events in Vue

Vue uses v-on for DOM events.

The shorthand is @.

For example:

<button @click="saveProduct">
  Save Product
</button>

Then:

function saveProduct() {
  console.log('Saving product')
}

Common events include:

@click
@submit
@input
@change
@focus
@keydown

Events connect user actions to your application logic.


Understanding Vue Directives

Vue directives are special template attributes.

Common directives include:

v-if
v-else
v-for
v-bind
v-on
v-model
v-show

These allow templates to react to application data.


Conditional Rendering with v-if

Suppose a product has a stock quantity:

<p v-if="stockQuantity > 0">
  In Stock
</p>

<p v-else>
  Out of Stock
</p>

Conditional rendering is useful for:

  • Loading states

  • Error messages

  • Empty results

  • Permissions

  • Stock status

  • Conditional buttons


Rendering Collections with v-for

Suppose you have:

const products = [
  {
    id: 1,
    name: 'Laptop',
    price: 75000
  },
  {
    id: 2,
    name: 'Keyboard',
    price: 2500
  }
]

Render the products:

<ul>
  <li
    v-for="product in products"
    :key="product.id"
  >
    {{ product.name }} - {{ product.price }}
  </li>
</ul>

The :key value helps Vue identify list items when the collection changes.


Understanding v-bind

Dynamic HTML attributes can use v-bind.

For example:

<img
  v-bind:src="productImage"
  v-bind:alt="productName"
/>

The common shorthand is:

<img
  :src="productImage"
  :alt="productName"
/>

You will see the : syntax frequently in Vue applications.


Creating Computed Values

Some values can be derived from existing reactive state.

Vue provides computed() for this purpose.

<script setup lang="ts">
import { ref, computed } from 'vue'

const price = ref(100)
const quantity = ref(3)

const total = computed(() => {
  return price.value * quantity.value
})
</script>

<template>
  <p>Total: {{ total }}</p>
</template>

When price or quantity changes, the computed result reflects the new values.

Computed properties are useful for:

  • Totals

  • Filtered lists

  • Calculated status

  • Display values

  • Derived data


Passing Data with Props

A parent component can pass data to a child using props.

Create:

ProductCard.vue

Then:

<script setup lang="ts">
defineProps<{
  name: string
  price: number
}>()
</script>

<template>
  <div class="product-card">
    <h3>{{ name }}</h3>
    <p>Price: {{ price }}</p>
  </div>
</template>

Use it:

<ProductCard
  name="Laptop"
  :price="75000"
/>

Props allow the same component to display different data.


Sending Events with Emits

A child component can communicate with its parent using emitted events.

<script setup lang="ts">
const emit = defineEmits<{
  selected: []
}>()

function selectProduct() {
  emit('selected')
}
</script>

<template>
  <button @click="selectProduct">
    Select
  </button>
</template>

The parent listens:

<ProductCard
  name="Laptop"
  :price="75000"
  @selected="handleProductSelected"
/>

A useful mental model is:

Parent
   ↓
Props
   ↓
Child

Child
   ↓
Events
   ↓
Parent

Building Forms with v-model

Vue uses v-model for form binding.

<script setup lang="ts">
import { ref } from 'vue'

const productName = ref('')
</script>

<template>
  <label for="product-name">
    Product Name
  </label>

  <input
    id="product-name"
    v-model="productName"
    type="text"
  />

  <p>{{ productName }}</p>
</template>

When the user types into the input, productName changes.

Vue then updates anywhere that value is used.


Creating a Product Type

Create:

src/models/Product.ts

Add:

export interface Product {
  id: number
  name: string
  price: number
  stockQuantity: number
}

For create and update requests, you can also define specific types:

export interface CreateProduct {
  name: string
  price: number
  stockQuantity: number
}

export interface UpdateProduct {
  name: string
  price: number
  stockQuantity: number
}

This makes the frontend API contract clearer.


Creating ASP.NET Core DTOs

On the backend, define explicit request DTOs.

public class CreateProductDto
{
    [Required]
    [StringLength(100)]
    public string Name { get; set; } = string.Empty;

    [Range(0.01, 1000000)]
    public decimal Price { get; set; }

    [Range(0, 1000000)]
    public int StockQuantity { get; set; }
}

For updates:

public class UpdateProductDto
{
    [Required]
    [StringLength(100)]
    public string Name { get; set; } = string.Empty;

    [Range(0.01, 1000000)]
    public decimal Price { get; set; }

    [Range(0, 1000000)]
    public int StockQuantity { get; set; }
}

DTOs keep the public API contract separate from database persistence concerns.


Creating the Product Entity

The Entity Framework Core entity might look like:

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }

    public int StockQuantity { get; set; }
}

The frontend TypeScript interface and backend C# entity represent similar information, but they belong to separate applications and should not be treated as the same object.


Connecting Entity Framework Core

Create:

public class AppDbContext : DbContext
{
    public AppDbContext(
        DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    public DbSet<Product> Products => Set<Product>();
}

The backend can now use Entity Framework Core to access persistent product data.


Building the ASP.NET Core Product API

Create a controller:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly AppDbContext _dbContext;

    public ProductsController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    [HttpGet]
    public async Task<ActionResult<List<Product>>> GetProducts()
    {
        var products = await _dbContext.Products
            .AsNoTracking()
            .ToListAsync();

        return Ok(products);
    }

    [HttpGet("{id:int}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _dbContext.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == id);

        if (product is null)
            return NotFound();

        return Ok(product);
    }
}

Vue now has endpoints for retrieving all products and retrieving one product.


Creating a Product with ASP.NET Core

Add:

[HttpPost]
public async Task<ActionResult<Product>> CreateProduct(
    CreateProductDto model)
{
    var product = new Product
    {
        Name = model.Name,
        Price = model.Price,
        StockQuantity = model.StockQuantity
    };

    _dbContext.Products.Add(product);

    await _dbContext.SaveChangesAsync();

    return CreatedAtAction(
        nameof(GetProduct),
        new { id = product.Id },
        product);
}

The API now validates the incoming DTO and creates a new database entity.


Updating a Product with ASP.NET Core

Add:

[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateProduct(
    int id,
    UpdateProductDto model)
{
    var product = await _dbContext.Products
        .FindAsync(id);

    if (product is null)
        return NotFound();

    product.Name = model.Name;
    product.Price = model.Price;
    product.StockQuantity = model.StockQuantity;

    await _dbContext.SaveChangesAsync();

    return NoContent();
}

The API retrieves the existing entity and updates only the intended fields.


Deleting a Product

Add:

[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteProduct(int id)
{
    var product = await _dbContext.Products
        .FindAsync(id);

    if (product is null)
        return NotFound();

    _dbContext.Products.Remove(product);

    await _dbContext.SaveChangesAsync();

    return NoContent();
}

The backend now supports the complete CRUD foundation.


Configure the Vue API URL

Avoid repeating a hard-coded API address.

Create:

.env

Add:

VITE_API_URL=https://localhost:7001

Access it:

const apiUrl = import.meta.env.VITE_API_URL

Remember that frontend environment variables are delivered to the client.

Never place secrets such as passwords, private API keys, or database connection strings in frontend environment variables.


Creating the Vue Product Service

Create:

src/services/productService.ts

Add:

import type {
  Product,
  CreateProduct,
  UpdateProduct
} from '@/models/Product'

const apiUrl = import.meta.env.VITE_API_URL

const productsUrl = `${apiUrl}/api/products`

export async function getProducts(): Promise<Product[]> {
  const response = await fetch(productsUrl)

  if (!response.ok) {
    throw new Error('Unable to load products')
  }

  return response.json()
}

export async function getProduct(
  id: number,
  signal?: AbortSignal
): Promise<Product> {
  const response = await fetch(
    `${productsUrl}/${id}`,
    { signal }
  )

  if (!response.ok) {
    throw new Error('Unable to load product')
  }

  return response.json()
}

export async function createProduct(
  product: CreateProduct
): Promise<Product> {
  const response = await fetch(productsUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(product)
  })

  if (!response.ok) {
    throw new Error('Unable to create product')
  }

  return response.json()
}

export async function updateProduct(
  id: number,
  product: UpdateProduct
): Promise<void> {
  const response = await fetch(
    `${productsUrl}/${id}`,
    {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(product)
    }
  )

  if (!response.ok) {
    throw new Error('Unable to update product')
  }
}

export async function deleteProduct(
  id: number
): Promise<void> {
  const response = await fetch(
    `${productsUrl}/${id}`,
    {
      method: 'DELETE'
    }
  )

  if (!response.ok) {
    throw new Error('Unable to delete product')
  }
}

Now components do not need to know the details of every API request.


Loading Products from ASP.NET Core

Create a product list view:

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type { Product } from '@/models/Product'
import {
  getProducts,
  deleteProduct
} from '@/services/productService'

const products = ref<Product[]>([])
const loading = ref(true)
const error = ref<string | null>(null)

async function loadProducts() {
  try {
    loading.value = true
    error.value = null

    products.value = await getProducts()
  } catch {
    error.value = 'Unable to load products.'
  } finally {
    loading.value = false
  }
}

async function removeProduct(id: number) {
  try {
    await deleteProduct(id)

    products.value = products.value.filter(
      product => product.id !== id
    )
  } catch {
    error.value = 'Unable to delete product.'
  }
}

onMounted(loadProducts)
</script>

<template>
  <h1>Products</h1>

  <p v-if="loading">
    Loading products...
  </p>

  <p v-else-if="error">
    {{ error }}
  </p>

  <p v-else-if="products.length === 0">
    No products found.
  </p>

  <div v-else>
    <div
      v-for="product in products"
      :key="product.id"
    >
      <strong>{{ product.name }}</strong>

      <span>
        {{ product.price }}
      </span>

      <button @click="removeProduct(product.id)">
        Delete
      </button>
    </div>
  </div>
</template>

The application now handles:

Loading
Success
Empty Result
Error

These states are important in real applications.


Avoiding Stale Requests

A component can sometimes disappear before an asynchronous request finishes.

The browser's AbortController can be useful when a request should be cancelled during component cleanup.

For example:

import {
  onMounted,
  onUnmounted,
  ref
} from 'vue'

const controller = new AbortController()

onMounted(async () => {
  try {
    product.value = await getProduct(
      productId,
      controller.signal
    )
  } catch (error) {
    if (
      error instanceof DOMException &&
      error.name === 'AbortError'
    ) {
      return
    }

    errorMessage.value = 'Unable to load product.'
  }
})

onUnmounted(() => {
  controller.abort()
})

You do not need this pattern for every simple request.

It becomes useful when requests can outlive the component that started them or when rapid navigation can make older responses irrelevant.


Configuring CORS

During development, Vue and ASP.NET Core often run on different origins.

For example:

Vue
http://localhost:5173

ASP.NET Core
https://localhost:7001

Configure ASP.NET Core:

builder.Services.AddCors(options =>
{
    options.AddPolicy("VuePolicy", policy =>
    {
        policy
            .WithOrigins("http://localhost:5173")
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

Then:

app.UseCors("VuePolicy");

Production applications should allow only the origins and operations they actually require.


Creating a Product Form

A create view can use reactive() because several related form fields belong together.

<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { createProduct } from '@/services/productService'

const router = useRouter()

const product = reactive({
  name: '',
  price: 0,
  stockQuantity: 0
})

const saving = ref(false)
const error = ref<string | null>(null)

async function saveProduct() {
  if (!product.name.trim()) {
    error.value = 'Product name is required.'
    return
  }

  try {
    saving.value = true
    error.value = null

    await createProduct(product)

    await router.push('/products')
  } catch {
    error.value = 'Unable to create product.'
  } finally {
    saving.value = false
  }
}
</script>

<template>
  <h1>Create Product</h1>

  <form @submit.prevent="saveProduct">
    <div>
      <label for="name">Product Name</label>

      <input
        id="name"
        v-model="product.name"
        type="text"
      />
    </div>

    <div>
      <label for="price">Price</label>

      <input
        id="price"
        v-model.number="product.price"
        type="number"
      />
    </div>

    <div>
      <label for="stock">
        Stock Quantity
      </label>

      <input
        id="stock"
        v-model.number="product.stockQuantity"
        type="number"
      />
    </div>

    <p v-if="error">
      {{ error }}
    </p>

    <button
      type="submit"
      :disabled="saving"
    >
      {{ saving ? 'Saving...' : 'Save Product' }}
    </button>
  </form>
</template>

This creates a complete flow:

Vue Form
   ↓
v-model
   ↓
Reactive Object
   ↓
Product Service
   ↓
POST Request
   ↓
ASP.NET Core
   ↓
Database
   ↓
Navigate to Product List

Adding Vue Router

A multi-page application needs routing.

Typical routes might include:

/
/products
/products/create
/products/10
/products/10/edit

A router configuration can look like:

import {
  createRouter,
  createWebHistory
} from 'vue-router'

import HomeView from '@/views/HomeView.vue'
import ProductListView from '@/views/products/ProductListView.vue'
import ProductCreateView from '@/views/products/ProductCreateView.vue'
import ProductEditView from '@/views/products/ProductEditView.vue'

const router = createRouter({
  history: createWebHistory(),

  routes: [
    {
      path: '/',
      component: HomeView
    },
    {
      path: '/products',
      component: ProductListView
    },
    {
      path: '/products/create',
      component: ProductCreateView
    },
    {
      path: '/products/:id/edit',
      component: ProductEditView
    }
  ]
})

export default router

Your root component can contain:

<RouterView />

Navigation links can use:

<RouterLink to="/products">
  Products
</RouterLink>

Reading Vue Route Parameters

Suppose the user visits:

/products/10/edit

The 10 is a route parameter.

Inside the edit view:

import { useRoute } from 'vue-router'

const route = useRoute()

const productId = Number(route.params.id)

You can now use that ID to retrieve the correct product.

Always consider invalid or missing IDs rather than assuming route data is automatically valid.


Building a Complete Product Edit Page

Create:

ProductEditView.vue

Then:

<script setup lang="ts">
import {
  onMounted,
  onUnmounted,
  reactive,
  ref
} from 'vue'

import {
  useRoute,
  useRouter
} from 'vue-router'

import {
  getProduct,
  updateProduct
} from '@/services/productService'

const route = useRoute()
const router = useRouter()

const productId = Number(route.params.id)

const product = reactive({
  name: '',
  price: 0,
  stockQuantity: 0
})

const loading = ref(true)
const saving = ref(false)
const error = ref<string | null>(null)

const controller = new AbortController()

async function loadProduct() {
  if (!Number.isInteger(productId) || productId <= 0) {
    error.value = 'Invalid product ID.'
    loading.value = false
    return
  }

  try {
    const data = await getProduct(
      productId,
      controller.signal
    )

    product.name = data.name
    product.price = data.price
    product.stockQuantity = data.stockQuantity
  } catch (err) {
    if (
      err instanceof DOMException &&
      err.name === 'AbortError'
    ) {
      return
    }

    error.value = 'Unable to load product.'
  } finally {
    loading.value = false
  }
}

async function saveProduct() {
  if (!product.name.trim()) {
    error.value = 'Product name is required.'
    return
  }

  try {
    saving.value = true
    error.value = null

    await updateProduct(
      productId,
      product
    )

    await router.push('/products')
  } catch {
    error.value = 'Unable to update product.'
  } finally {
    saving.value = false
  }
}

onMounted(loadProduct)

onUnmounted(() => {
  controller.abort()
})
</script>

<template>
  <h1>Edit Product</h1>

  <p v-if="loading">
    Loading product...
  </p>

  <p v-else-if="error && !product.name">
    {{ error }}
  </p>

  <form
    v-else
    @submit.prevent="saveProduct"
  >
    <div>
      <label for="name">Product Name</label>

      <input
        id="name"
        v-model="product.name"
        type="text"
      />
    </div>

    <div>
      <label for="price">Price</label>

      <input
        id="price"
        v-model.number="product.price"
        type="number"
      />
    </div>

    <div>
      <label for="stock">
        Stock Quantity
      </label>

      <input
        id="stock"
        v-model.number="product.stockQuantity"
        type="number"
      />
    </div>

    <p v-if="error">
      {{ error }}
    </p>

    <button
      type="submit"
      :disabled="saving"
    >
      {{ saving ? 'Saving...' : 'Update Product' }}
    </button>
  </form>
</template>

This is an important step because it connects several Vue concepts in one real workflow:

Route Parameter
      ↓
Load Product
      ↓
Reactive Form
      ↓
v-model
      ↓
PUT Request
      ↓
ASP.NET Core
      ↓
Entity Framework Core
      ↓
Database
      ↓
Return to Product List

Client-Side and Server-Side Validation

Vue can validate a form before sending it.

For example:

if (!product.name.trim()) {
  error.value = 'Product name is required.'
  return
}

This improves user experience.

However, frontend validation can be bypassed.

ASP.NET Core should always validate the incoming request independently.

A useful rule is:

Vue Validation
      ↓
User Experience

ASP.NET Core Validation
      ↓
Application and Data Protection

Client-side validation complements server-side validation. It does not replace it.


Understanding Vue CRUD

Our application now supports:

OperationHTTP MethodEndpoint
CreatePOST/api/products
Read AllGET/api/products
Read OneGET/api/products/{id}
UpdatePUT/api/products/{id}
DeleteDELETE/api/products/{id}

The complete architecture is:

Vue View
   ↓
Vue Component State
   ↓
Product Service
   ↓
HTTP / JSON
   ↓
ASP.NET Core Controller
   ↓
DTO / Validation
   ↓
Entity Framework Core
   ↓
Database

This same pattern can be reused for:

Customers
Suppliers
Categories
Orders
Purchases
Sales
Employees

Understanding Vue Lifecycle Hooks

Vue components move through different lifecycle stages.

With the Composition API, common hooks include:

onMounted
onUpdated
onUnmounted

onMounted() is commonly used when initial work needs to happen after the component is mounted.

For example:

onMounted(loadProducts)

onUnmounted() is useful for cleanup:

onUnmounted(() => {
  controller.abort()
})

Do not use lifecycle hooks simply because they are available.

Use them when the timing of the component lifecycle matters.


Understanding watch()

Sometimes an action should happen when reactive state changes.

Vue provides watch().

For example:

import { ref, watch } from 'vue'

const searchText = ref('')

watch(searchText, newValue => {
  console.log('Search changed:', newValue)
})

Watchers are useful for side effects such as:

  • Loading data

  • Synchronizing state

  • Responding to filters

  • Reacting to route-related values


computed() vs watch()

These two features solve different problems.

Use computed() when you need derived data.

For example:

Price × Quantity
      ↓
Total

Use watch() when a state change should trigger an action.

For example:

Search Text Changes
      ↓
Request Search Results

If you only need a calculated value, a watcher is usually unnecessary.


Creating Reusable Composables

As applications grow, multiple components may need similar stateful logic.

Vue's Composition API allows reusable functions called composables.

Examples include:

useProducts
usePagination
useSearch
useAuthentication

A simple composable might look like:

import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)

  function increment() {
    count.value++
  }

  return {
    count,
    increment
  }
}

A component can now reuse the logic without duplicating it.

Do not create composables simply to move every function into another file.

They are most useful when logic is reusable or when extraction makes a component significantly clearer.


A Practical Vue and ASP.NET Core Project Structure

A small Vue frontend might look like:

src
│
├── components
│   └── products
│       └── ProductCard.vue
│
├── views
│   └── products
│       ├── ProductListView.vue
│       ├── ProductCreateView.vue
│       ├── ProductEditView.vue
│       └── ProductDetailsView.vue
│
├── models
│   └── Product.ts
│
├── services
│   └── productService.ts
│
├── composables
│   └── useProducts.ts
│
└── router
    └── index.ts

The ASP.NET Core application might contain:

API
│
├── Controllers
├── DTOs
├── Entities
├── Services
├── Data
└── Validation

There is no single folder structure that every project must follow.

The important goal is clear responsibility.


Authentication Between Vue and ASP.NET Core

Business applications commonly need authentication.

A simplified flow is:

Vue Login Form
      ↓
ASP.NET Core
      ↓
Validate Credentials
      ↓
Authentication Result
      ↓
Vue Application

Protected backend operations should still enforce authorization on the server.

Remember:

Authentication answers: Who is the user?

Authorization answers: What is the user allowed to do?

Security deserves its own implementation rather than being reduced to a few token-handling lines inside a beginner CRUD example.


Common Vue Mistakes Beginners Should Avoid

Learning Vue Without JavaScript Fundamentals

Vue does not replace JavaScript.

Understanding JavaScript makes Vue easier to use and debug.

Putting Everything in One Component

Separate large interfaces into meaningful components and views.

Calling APIs from Every Component

Direct fetch() calls are fine while learning, but a service layer can reduce duplication as the application grows.

Using watch() for Every Calculation

Use computed() for derived values.

Use watch() when a state change needs to trigger a side effect.

Assuming ref() Is Only for Primitive Values

ref() can also hold objects and arrays.

Understand the behavior of both ref() and reactive() instead of applying rigid rules.

Ignoring Request Cleanup

Not every request requires cancellation, but long-running requests or rapidly changing views can create stale responses.

Understand when cleanup is useful.

Forgetting Loading and Error States

Every API-based interface should consider:

Loading
Success
Empty
Error

Mutating Props

Props belong to the parent-to-child data flow.

Use emitted events or another intentional state pattern when the child needs to communicate changes.

Storing Secrets in Frontend Environment Variables

Frontend environment variables are accessible to client-side code.

Never store private server credentials there.

Trusting Client-Side Validation

The backend must validate requests independently.

Exposing Database Entities as Your Long-Term API Contract

Simple tutorials can use entities to keep examples understandable.

Larger applications benefit from explicit DTOs and clearer boundaries.


Vue Learning Path for Beginners

A practical learning sequence is:

Step 1: JavaScript Fundamentals

Learn functions, objects, arrays, modules, promises, and async programming.

Step 2: TypeScript

Learn types, interfaces, functions, generics, and optional properties.

Step 3: Vue Components

Understand Single-File Components, templates, and <script setup>.

Step 4: Reactivity

Learn:

ref()
reactive()
computed()

Step 5: Vue Directives

Practice:

v-if
v-for
v-bind
v-on
v-model

Step 6: Component Communication

Learn props and emitted events.

Step 7: Forms

Build real forms with validation and submit handling.

Step 8: API Communication

Practice GET, POST, PUT, and DELETE.

Step 9: Vue Router

Create multiple views and work with route parameters.

Step 10: Composition API

Learn lifecycle hooks, watchers, and composables.

Step 11: ASP.NET Core Integration

Build APIs with DTOs, validation, application services, and Entity Framework Core.

Step 12: Authentication and Authorization

Secure your application correctly on the backend.

Step 13: Build a Complete Application

Combine the concepts into a real project.


A Good First Vue and C# Project

A Product or Inventory Management application is a practical starting point.

Begin with:

Product List
Product Details
Create Product
Edit Product
Delete Product

Then add:

Categories
Search
Filtering
Pagination
Validation
Loading States
Error Handling

Next introduce:

Entity Framework Core
SQL Server or PostgreSQL
Authentication
Authorization
User Roles

Later expand the same application with:

Customers
Suppliers
Purchases
Sales
Stock
Reports

Building one application progressively gives you a much clearer understanding of how Vue and ASP.NET Core work together.


Vue vs ASP.NET Core: What Is the Difference?

Vue and ASP.NET Core solve different parts of the application.

TechnologyMain Responsibility
VueFrontend user interface
JavaScript / TypeScriptFrontend programming
Vue RouterClient-side routing
C#Backend programming
ASP.NET CoreAPI and server-side application
Entity Framework CoreData access
SQL Server / PostgreSQLPersistent data

They work together:

Vue
   ↓
Components
   ↓
Services
   ↓
HTTP / JSON
   ↓
ASP.NET Core
   ↓
C# Application Logic
   ↓
Entity Framework Core
   ↓
Database

Frequently Asked Questions

Is Vue Good for Beginners?

Vue can be approachable for developers who already understand basic HTML, CSS, and JavaScript.

Its template syntax allows beginners to start with familiar web concepts before moving into more advanced application architecture.

Can Vue Work with C#?

Yes.

Vue can communicate with a C# backend through an ASP.NET Core Web API.

Can Vue Work with ASP.NET Core?

Yes.

A common architecture is:

Vue
   ↓
ASP.NET Core Web API
   ↓
Entity Framework Core
   ↓
Database

Should I Learn JavaScript Before Vue?

Yes.

JavaScript fundamentals make Vue much easier to understand and debug.

Should I Use TypeScript with Vue?

You can use either JavaScript or TypeScript.

TypeScript becomes particularly useful as an application grows and API contracts and application models become more complex.

What Is the Vue Composition API?

The Composition API provides APIs such as ref(), reactive(), computed(), watch(), and lifecycle hooks for organizing component state and behavior.

What Is a Vue Single-File Component?

A Single-File Component is a .vue file that can contain a component's template, logic, and styles.

What Is the Difference Between ref() and reactive()?

Both can create reactive state.

ref() wraps a value and uses .value in JavaScript or TypeScript code.

reactive() creates a reactive proxy for an object and allows direct property access.

Both are useful, and the right choice depends on how you want to organize state.

Can Vue Use SQL Server?

Vue should not normally connect directly to SQL Server.

Instead:

Vue
   ↓
ASP.NET Core
   ↓
Entity Framework Core
   ↓
SQL Server

Can Vue Use PostgreSQL?

Yes, through the backend.

ASP.NET Core can communicate with PostgreSQL while Vue communicates with ASP.NET Core through HTTP.

Vue or Blazor for a C# Developer?

Both can work well with .NET systems.

Vue uses JavaScript or TypeScript and the Vue frontend ecosystem.

Blazor allows developers to build interactive components using C# and Razor.

The choice should depend on application requirements, architecture, team skills, ecosystem needs, and deployment strategy.


What You Built in This Tutorial

We started with a basic Vue component and gradually built the foundation of a full stack CRUD application.

You learned how to:

  • Create Vue Single-File Components

  • Use <script setup>

  • Work with TypeScript

  • Create reactive state with ref()

  • Use reactive() for objects

  • Understand ref() vs reactive()

  • Handle Vue events

  • Use directives

  • Render lists and conditions

  • Create computed values

  • Pass props

  • Emit events

  • Build forms with v-model

  • Create frontend API types

  • Create ASP.NET Core DTOs

  • Connect Entity Framework Core

  • Build GET, POST, PUT, and DELETE endpoints

  • Create a reusable Vue API service

  • Handle loading, empty, and error states

  • Configure CORS

  • Add Vue Router

  • Read route parameters

  • Build a complete edit workflow

  • Understand lifecycle hooks

  • Cancel stale requests when appropriate

  • Understand watch()

  • Create reusable composables

The complete application flow is now:

User
   ↓
Vue View
   ↓
Reactive State
   ↓
Vue Service
   ↓
HTTP / JSON
   ↓
ASP.NET Core API
   ↓
DTO and Validation
   ↓
Application / Data Logic
   ↓
Entity Framework Core
   ↓
Database

That is the foundation of a real Vue and C# full stack application.


What Should You Learn Next?

After understanding these fundamentals, useful next topics include:

  • Complete Vue CRUD architecture

  • Reusable form components

  • Advanced Vue Router

  • Search and filtering

  • Pagination

  • Form validation

  • State management

  • Authentication

  • Role-based authorization

  • File uploads

  • API error handling

  • Testing

  • Performance optimization

  • Production deployment

Do not try to learn the entire Vue ecosystem before building a project.

Build something useful and introduce new concepts when the application gives you a reason to use them.


Final Thoughts

Vue gives developers a clean way to build interactive, component-based frontend applications.

For C# developers, it can fit naturally into an architecture where Vue manages the browser experience while ASP.NET Core remains responsible for APIs, application rules, validation, security, and persistent data.

A useful mental model is:

Vue Components
      ↓
Reactive State
      ↓
Vue Services
      ↓
ASP.NET Core API
      ↓
C# Application Logic
      ↓
Entity Framework Core
      ↓
Database

Start with components.

Learn ref(), reactive(), directives, props, events, and v-model.

Then connect a small ASP.NET Core API.

Build a product list.

Create a product form.

Add an edit route.

Implement update and delete.

Connect Entity Framework Core.

Once that flow makes sense, move into authentication, authorization, state management, testing, performance, and deployment.

The most effective way to learn Vue is not to memorize every API in the framework.

Build a small application, understand why each feature exists, and expand the project one real requirement at a time.

Comments 0