Nedo.AspNet.Scm 2.0.0

dotnet add package Nedo.AspNet.Scm --version 2.0.0
                    
NuGet\Install-Package Nedo.AspNet.Scm -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nedo.AspNet.Scm" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nedo.AspNet.Scm" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Nedo.AspNet.Scm" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nedo.AspNet.Scm --version 2.0.0
                    
#r "nuget: Nedo.AspNet.Scm, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nedo.AspNet.Scm@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nedo.AspNet.Scm&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Nedo.AspNet.Scm&version=2.0.0
                    
Install as a Cake Tool

Nedo.AspNet.Scm

NuGet License: MIT

A professional, enterprise-grade Source Control Management (SCM) library for .NET applications. This library provides a unified, provider-agnostic interface for interacting with various Git hosting platforms including GitLab, GitHub, Azure DevOps, and more.

Features

Provider-Agnostic Architecture - Write code once, switch providers easily
🏗️ Clean Architecture - Well-structured, maintainable, and testable code
🔌 Easy Integration - Simple dependency injection setup
📦 Comprehensive API - Complete support for repositories, branches, commits, pull requests, tags, and users
🚀 Async/Await - Fully asynchronous API for better performance
Type-Safe - Strongly-typed models with nullable reference types
🎯 Future-Proof - Designed to support multiple Git providers
📝 Well-Documented - XML documentation for all public APIs

Currently Supported Providers

  • GitLab (v4 API)
  • 🔜 GitHub (coming soon)
  • 🔜 Azure DevOps (coming soon)
  • 🔜 Bitbucket (coming soon)

Installation

Install via NuGet Package Manager:

dotnet add package Nedo.AspNet.Scm

Or via Package Manager Console:

Install-Package Nedo.AspNet.Scm

Quick Start

1. Configuration

Add GitLab configuration to your appsettings.json:

{
  "GitLab": {
    "BaseUrl": "https://gitlab.com",
    "AccessToken": "your-personal-access-token",
    "ApiVersion": "v4",
    "TimeoutSeconds": 30,
    "EnableRetry": true,
    "MaxRetryAttempts": 3
  }
}

2. Register Services

In your Program.cs or Startup.cs:

using Nedo.AspNet.Scm.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Add GitLab SCM provider
builder.Services.AddGitLabScm(builder.Configuration);

// Or with inline configuration
builder.Services.AddGitLabScm(options =>
{
    options.BaseUrl = "https://gitlab.com";
    options.AccessToken = "your-access-token";
});

// Or with simple parameters
builder.Services.AddGitLabScm(
    baseUrl: "https://gitlab.com",
    accessToken: "your-access-token"
);

var app = builder.Build();

3. Usage Examples

Working with Repositories
using Nedo.AspNet.Scm.Core.Interfaces;
using Nedo.AspNet.Scm.Core.Models;

public class RepositoryController : ControllerBase
{
    private readonly IScmProvider _scm;

    public RepositoryController(IScmProvider scm)
    {
        _scm = scm;
    }

    // List all repositories
    public async Task<IActionResult> GetRepositories()
    {
        var repositories = await _scm.Repositories.ListAsync(new RepositoryListOptions
        {
            Owned = true,
            Visibility = RepositoryVisibility.Private,
            Page = 1,
            PerPage = 20
        });

        return Ok(repositories);
    }

    // Get repository by ID
    public async Task<IActionResult> GetRepository(string id)
    {
        var repository = await _scm.Repositories.GetByIdAsync(id);
        return Ok(repository);
    }

    // Create a new repository
    public async Task<IActionResult> CreateRepository()
    {
        var repository = await _scm.Repositories.CreateAsync(new CreateRepositoryRequest
        {
            Name = "my-new-project",
            Description = "A new project created via API",
            Visibility = RepositoryVisibility.Private,
            InitializeWithReadme = true,
            DefaultBranch = "main"
        });

        return Ok(repository);
    }

    // Get file content
    public async Task<IActionResult> GetFileContent(string repoId, string filePath)
    {
        var file = await _scm.Repositories.GetFileContentAsync(
            repoId,
            filePath,
            reference: "main"
        );

        return Ok(file?.Content);
    }
}
Working with Branches
public class BranchService
{
    private readonly IScmProvider _scm;

    public BranchService(IScmProvider scm)
    {
        _scm = scm;
    }

    // List all branches
    public async Task<IEnumerable<Branch>> GetBranches(string repositoryId)
    {
        return await _scm.Branches.ListAsync(repositoryId);
    }

    // Create a new branch
    public async Task<Branch> CreateFeatureBranch(string repositoryId, string featureName)
    {
        return await _scm.Branches.CreateAsync(repositoryId, new CreateBranchRequest
        {
            BranchName = $"feature/{featureName}",
            Reference = "main"
        });
    }

    // Protect a branch
    public async Task<Branch> ProtectBranch(string repositoryId, string branchName)
    {
        return await _scm.Branches.ProtectAsync(repositoryId, branchName,
            new BranchProtectionOptions
            {
                RequireCodeReview = true,
                RequiredApprovalsCount = 2,
                AllowForcePush = false
            });
    }

    // Delete a branch
    public async Task DeleteBranch(string repositoryId, string branchName)
    {
        await _scm.Branches.DeleteAsync(repositoryId, branchName);
    }
}
Working with Pull Requests (Merge Requests)
public class PullRequestService
{
    private readonly IScmProvider _scm;

    public PullRequestService(IScmProvider scm)
    {
        _scm = scm;
    }

    // Create a pull request
    public async Task<PullRequest> CreatePullRequest(string repositoryId)
    {
        return await _scm.PullRequests.CreateAsync(repositoryId,
            new CreatePullRequestRequest
            {
                Title = "Add new feature",
                Description = "This PR adds a new feature",
                SourceBranch = "feature/new-feature",
                TargetBranch = "main",
                Labels = new List<string> { "enhancement", "high-priority" },
                RemoveSourceBranch = true
            });
    }

    // List open pull requests
    public async Task<IEnumerable<PullRequest>> GetOpenPullRequests(string repositoryId)
    {
        return await _scm.PullRequests.ListAsync(repositoryId,
            new PullRequestListOptions
            {
                State = PullRequestState.Open,
                OrderBy = "updated_at",
                Sort = "desc"
            });
    }

    // Merge a pull request
    public async Task<PullRequest> MergePullRequest(string repositoryId, int prId)
    {
        return await _scm.PullRequests.MergeAsync(repositoryId, prId,
            new MergePullRequestOptions
            {
                MergeCommitMessage = "Merged via API",
                RemoveSourceBranch = true,
                Squash = false
            });
    }

    // Add comment to pull request
    public async Task<Comment> AddComment(string repositoryId, int prId, string comment)
    {
        return await _scm.PullRequests.AddCommentAsync(repositoryId, prId, comment);
    }

    // Get PR changes
    public async Task<IEnumerable<FileDiff>> GetChanges(string repositoryId, int prId)
    {
        return await _scm.PullRequests.GetChangesAsync(repositoryId, prId);
    }
}
Working with Commits
public class CommitService
{
    private readonly IScmProvider _scm;

    public CommitService(IScmProvider scm)
    {
        _scm = scm;
    }

    // List commits
    public async Task<IEnumerable<Commit>> GetCommits(string repositoryId)
    {
        return await _scm.Commits.ListAsync(repositoryId, new CommitListOptions
        {
            RefName = "main",
            Since = DateTimeOffset.UtcNow.AddDays(-7),
            PerPage = 50
        });
    }

    // Create a commit with file changes
    public async Task<Commit> CreateCommit(string repositoryId)
    {
        return await _scm.Commits.CreateAsync(repositoryId, new CreateCommitRequest
        {
            Branch = "main",
            Message = "Update configuration files",
            Actions = new List<FileAction>
            {
                new FileAction
                {
                    Action = FileActionType.Create,
                    FilePath = "config/settings.json",
                    Content = "{\"version\": \"1.0.0\"}"
                },
                new FileAction
                {
                    Action = FileActionType.Update,
                    FilePath = "README.md",
                    Content = "# Updated README"
                }
            }
        });
    }

    // Get commit details
    public async Task<Commit> GetCommit(string repositoryId, string sha)
    {
        return await _scm.Commits.GetAsync(repositoryId, sha);
    }

    // Compare two branches
    public async Task<CommitComparison> CompareBranches(
        string repositoryId,
        string baseBranch,
        string headBranch)
    {
        return await _scm.Commits.CompareAsync(repositoryId, baseBranch, headBranch);
    }
}
Working with Tags
public class TagService
{
    private readonly IScmProvider _scm;

    public TagService(IScmProvider scm)
    {
        _scm = scm;
    }

    // Create a release tag
    public async Task<Tag> CreateReleaseTag(string repositoryId, string version)
    {
        return await _scm.Tags.CreateAsync(repositoryId, new CreateTagRequest
        {
            TagName = $"v{version}",
            Reference = "main",
            Message = $"Release version {version}",
            ReleaseDescription = "## What's Changed\n- Feature 1\n- Bug fix 2"
        });
    }

    // List all tags
    public async Task<IEnumerable<Tag>> GetTags(string repositoryId)
    {
        return await _scm.Tags.ListAsync(repositoryId);
    }
}
Working with Users
public class UserService
{
    private readonly IScmProvider _scm;

    public UserService(IScmProvider scm)
    {
        _scm = scm;
    }

    // Get current authenticated user
    public async Task<User> GetCurrentUser()
    {
        return await _scm.Users.GetCurrentUserAsync();
    }

    // Search for users
    public async Task<IEnumerable<User>> SearchUsers(string query)
    {
        return await _scm.Users.SearchAsync(query);
    }
}

Architecture

The library follows Clean Architecture principles:

src/
├── Core/                      # Core abstractions (provider-agnostic)
│   ├── Interfaces/           # Service interfaces
│   ├── Models/               # Domain models
│   └── Exceptions/           # Custom exceptions
├── Providers/
│   └── GitLab/               # GitLab implementation
│       ├── Services/         # GitLab API services
│       ├── Models/           # GitLab DTOs
│       └── Configuration/    # GitLab configuration
└── Extensions/               # DI extensions

Key Design Principles

  • SOLID Principles: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion
  • Dependency Injection: All services are injectable
  • Async/Await: Non-blocking I/O operations
  • Provider Pattern: Easy to add new Git providers
  • Clean Separation: Core logic separated from provider implementations

Error Handling

The library provides specific exception types for different scenarios:

try
{
    var repo = await _scm.Repositories.GetByIdAsync("123");
}
catch (ScmNotFoundException ex)
{
    // Resource not found (404)
    Console.WriteLine($"{ex.ResourceType} not found: {ex.Identifier}");
}
catch (ScmAuthenticationException ex)
{
    // Authentication failed (401)
    Console.WriteLine("Invalid or expired access token");
}
catch (ScmAuthorizationException ex)
{
    // Insufficient permissions (403)
    Console.WriteLine("Access forbidden");
}
catch (ScmRateLimitException ex)
{
    // Rate limit exceeded (429)
    Console.WriteLine($"Rate limit exceeded. Retry after {ex.RetryAfterSeconds} seconds");
}
catch (ScmConflictException ex)
{
    // Conflict (409) - e.g., merge conflict
    Console.WriteLine($"Conflict: {ex.Message}");
}
catch (ScmException ex)
{
    // General SCM error
    Console.WriteLine($"SCM error: {ex.Message}");
}

Advanced Usage

Multiple Provider Support (Future)

// When multiple providers are supported
builder.Services.AddGitLabScm(builder.Configuration.GetSection("GitLab"));
builder.Services.AddGitHubScm(builder.Configuration.GetSection("GitHub"));
builder.Services.AddScmProviderFactory();

// In your service
public class MyService
{
    private readonly IScmProviderFactory _factory;

    public MyService(IScmProviderFactory factory)
    {
        _factory = factory;
    }

    public async Task DoSomething()
    {
        var gitlab = _factory.GetProvider("GitLab");
        var github = _factory.GetProvider("GitHub");

        // Use different providers as needed
    }
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Roadmap

  • GitLab provider implementation
  • GitHub provider implementation
  • Azure DevOps provider implementation
  • Bitbucket provider implementation
  • Webhook support
  • CI/CD pipeline integration
  • Advanced caching strategies
  • GraphQL support for providers that offer it

Support

For issues, questions, or contributions, please visit the GitHub repository.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 108 2/7/2026
1.2.1 85 2/3/2026
1.2.0 84 2/3/2026
1.0.1 108 2/1/2026
1.0.0 105 2/1/2026