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
<PackageReference Include="Nedo.AspNet.Scm" Version="2.0.0" />
<PackageVersion Include="Nedo.AspNet.Scm" Version="2.0.0" />
<PackageReference Include="Nedo.AspNet.Scm" />
paket add Nedo.AspNet.Scm --version 2.0.0
#r "nuget: Nedo.AspNet.Scm, 2.0.0"
#:package Nedo.AspNet.Scm@2.0.0
#addin nuget:?package=Nedo.AspNet.Scm&version=2.0.0
#tool nuget:?package=Nedo.AspNet.Scm&version=2.0.0
Nedo.AspNet.Scm
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 | Versions 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. |
-
net9.0
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Http (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.0)
- System.Text.Json (>= 9.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.