Skip to content

Commit aff728d

Browse files
committed
Added the initials features for the manage of the short links
1 parent aca9348 commit aff728d

19 files changed

+3907
-2
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bin/Debug
2+
obj

Controllers/ShortenerController.cs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.Extensions.Logging;
8+
using JCTools.Shortener.Models;
9+
using JCTools.Shortener.Settings;
10+
using Microsoft.EntityFrameworkCore;
11+
using Microsoft.AspNetCore.Authorization;
12+
13+
namespace JCTools.Shortener.Controllers
14+
{
15+
[Authorize(Settings.Options.PolicyName)]
16+
public class ShortenerController<TDatabaseContext> : Controller
17+
where TDatabaseContext : DbContext, IDatabaseContext
18+
{
19+
/// <summary>
20+
/// The logger instance to be use for log the application messages
21+
/// </summary>
22+
private readonly ILogger<ShortenerController<TDatabaseContext>> _logger;
23+
/// <summary>
24+
/// the database context instance to be use
25+
/// </summary>
26+
private readonly TDatabaseContext _context;
27+
28+
public ShortenerController(
29+
ILogger<ShortenerController<TDatabaseContext>> logger,
30+
TDatabaseContext context
31+
)
32+
{
33+
_logger = logger;
34+
this._context = context;
35+
}
36+
37+
/// <summary>
38+
/// Valid and redirect from a shorted link to the related url
39+
/// </summary>
40+
/// <param name="token">The token to be use for the redirection</param>
41+
/// <returns>The task to be execute</returns>
42+
[HttpGet]
43+
[Route("/{token}")]
44+
public async Task<IActionResult> RedirectTo(string token)
45+
{
46+
var link = await _context.ShortLinks.FirstOrDefaultAsync(sl => sl.Token == token);
47+
if (link == null)
48+
return NotFound();
49+
50+
return Redirect(link.RelatedUrl);
51+
}
52+
}
53+
}

JCTools.Shortener.csproj

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
<OutputType>Library</OutputType>
6+
<IsPackable>true</IsPackable>
7+
<PackageId>JCTools.Shortener</PackageId>
8+
<Version>1.0.4</Version>
9+
<Authors>JeanCarlo13</Authors>
10+
<Company>JCTools</Company>
11+
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
12+
<Description>A simple links shortener for include in .net core projects</Description>
13+
<Copyright></Copyright>
14+
15+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
16+
17+
<PackageProjectUrl>https://github.com/jeancarlo13/JCTools.Shortener</PackageProjectUrl>
18+
<RepositoryUrl>https://github.com/jeancarlo13/JCTools.Shortener</RepositoryUrl>
19+
<PackageTags>.Net Core, Asp.Net Core, MVC, link, links, shortener, links shortener</PackageTags>
20+
</PropertyGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
24+
</ItemGroup>
25+
26+
</Project>

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 jeancarlo13
3+
Copyright (c) 2020 JCTools
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Models/ShortLink.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.ComponentModel.DataAnnotations.Schema;
3+
4+
namespace JCTools.Shortener.Models
5+
{
6+
[Table("Short_Link")]
7+
public class ShortLink
8+
{
9+
/// <summary>
10+
/// The random unique token to be used
11+
/// </summary>
12+
[Key]
13+
public string Token { get; set; }
14+
/// <summary>
15+
/// the url was shorted
16+
/// </summary>
17+
public string RelatedUrl { get; set; }
18+
}
19+
}

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# JCTools.Shortener
2-
A simple link shortener for include in .net core projects
2+
A simple links shortener for include in .net core projects

Services/ILinkGenerator.cs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Threading.Tasks;
2+
using JCTools.Shortener.Models;
3+
4+
namespace JCTools.Shortener.Services
5+
{
6+
public interface ILinkGenerator
7+
{
8+
9+
/// <summary>
10+
/// Generate and save a short link with a random token
11+
/// </summary>
12+
/// <param name="relatedTo">the related url to be short</param>
13+
/// <returns>The task to be execute</returns>
14+
Task<ShortLink> GenerateAndSaveAsync(string relatedTo);
15+
16+
/// <summary>
17+
/// Generate a short url with an random unique token
18+
/// </summary>
19+
/// <param name="relatedTo">the related url to be short</param>
20+
/// <returns>The generate shorted link</returns>
21+
Task<ShortLink> GenerateAsync(string relatedTo);
22+
/// <summary>
23+
/// Generate a random unique string token, with a configured length.
24+
/// </summary>
25+
/// <returns>The generate token</returns>
26+
Task<string> GenerateTokenAsync();
27+
/// <summary>
28+
/// Allows get the absolute Url for the specified short link
29+
/// </summary>
30+
/// <param name="link">The short link to be use for generate the absolute Url</param>
31+
/// <returns>The generated absolute url</returns>
32+
string GetAbsoluteUrl(ShortLink link);
33+
/// <summary>
34+
/// Allows get the absolute Url for the specified toke string
35+
/// </summary>
36+
/// <param name="token">The toke to be use for generate the absolute Url</param>
37+
/// <returns>The generated absolute url</returns>
38+
string GetAbsoluteUrl(string token);
39+
}
40+
}

Services/LinkGenerator.cs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using JCTools.Shortener.Models;
5+
using JCTools.Shortener.Settings;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.AspNetCore.Mvc.Infrastructure;
9+
using Microsoft.AspNetCore.Mvc.Routing;
10+
using Microsoft.EntityFrameworkCore;
11+
12+
13+
namespace JCTools.Shortener.Services
14+
{
15+
internal class LinkGenerator<TDatabaseContext> : ILinkGenerator
16+
where TDatabaseContext : DbContext, IDatabaseContext
17+
{
18+
/// <summary>
19+
/// the database context to be use
20+
/// </summary>
21+
private readonly TDatabaseContext _context;
22+
private readonly IUrlHelper _urlHelper;
23+
public LinkGenerator(
24+
TDatabaseContext context,
25+
IUrlHelperFactory urlHelperFactory,
26+
IActionContextAccessor actionContextAccessor
27+
)
28+
{
29+
this._context = context;
30+
this._urlHelper = urlHelperFactory.GetUrlHelper(actionContextAccessor.ActionContext);
31+
}
32+
33+
/// <summary>
34+
/// Generate a random unique string token, with a configured length.
35+
/// </summary>
36+
/// <returns>The generate token</returns>
37+
public async Task<string> GenerateTokenAsync()
38+
{
39+
var random = new Random();
40+
var token = string.Empty;
41+
var isValid = false;
42+
43+
while (!isValid)
44+
{
45+
var chars = Enumerable.Range(0, StartupExtensors.Options.ValidCharacters.Length - 1)
46+
.OrderBy(o => random.Next())
47+
.Select(i => StartupExtensors.Options.ValidCharacters[i])
48+
.ToList();
49+
50+
token = string.Join("", chars);
51+
var length = random.Next(StartupExtensors.Options.MinLength, StartupExtensors.Options.MaxLength);
52+
var start = random.Next(0, token.Length - length - 1);
53+
54+
token = token.Substring(start, length);
55+
56+
isValid = !await _context.ShortLinks.AnyAsync(sl => sl.Token == token);
57+
}
58+
59+
return token;
60+
}
61+
62+
/// <summary>
63+
/// Generate a short url with an random unique token
64+
/// </summary>
65+
/// <param name="relatedTo">the related url to be short</param>
66+
/// <returns>The generate shorted link</returns>
67+
public async Task<ShortLink> GenerateAsync(string relatedTo)
68+
{
69+
var tokenEntry = new ShortLink()
70+
{
71+
Token = await GenerateTokenAsync(),
72+
RelatedUrl = relatedTo
73+
};
74+
75+
return tokenEntry;
76+
}
77+
78+
/// <summary>
79+
/// Generate and save a short link with a random token
80+
/// </summary>
81+
/// <param name="relatedTo">the related url to be short</param>
82+
/// <returns>The task to be execute</returns>
83+
public async Task<ShortLink> GenerateAndSaveAsync(string relatedTo)
84+
{
85+
var link = await GenerateAsync(relatedTo);
86+
await _context.AddAsync(link);
87+
await _context.SaveChangesAsync();
88+
return link;
89+
}
90+
/// <summary>
91+
/// Allows get the absolute Url for the specified short link
92+
/// </summary>
93+
/// <param name="link">The short link to be use for generate the absolute Url</param>
94+
/// <returns>The generated absolute url</returns>
95+
public string GetAbsoluteUrl(ShortLink link) => GetAbsoluteUrl(link.Token);
96+
97+
/// <summary>
98+
/// Allows get the absolute Url for the specified toke string
99+
/// </summary>
100+
/// <param name="token">The toke to be use for generate the absolute Url</param>
101+
/// <returns>The generated absolute url</returns>
102+
public string GetAbsoluteUrl(string token)
103+
{
104+
return this._urlHelper.Action(
105+
nameof(Controllers.ShortenerController<TDatabaseContext>.RedirectTo),
106+
nameof(Controllers.ShortenerController<TDatabaseContext>).Replace("Controller", ""),
107+
new { token = token },
108+
this._urlHelper.ActionContext.HttpContext.Request.Scheme
109+
);
110+
}
111+
}
112+
}

Settings/IDatabaseContext.cs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using JCTools.Shortener.Models;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace JCTools.Shortener.Settings
5+
{
6+
/// <summary>
7+
/// Allows acccess to the database of the third-part application
8+
/// </summary>
9+
public interface IDatabaseContext
10+
{
11+
/// <summary>
12+
/// The collection of the generated short links
13+
/// </summary>
14+
DbSet<ShortLink> ShortLinks { get; set; }
15+
16+
}
17+
}

Settings/Options.cs

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using Microsoft.AspNetCore.Authorization;
3+
4+
namespace JCTools.Shortener.Settings
5+
{
6+
public class Options
7+
{
8+
/// <summary>
9+
/// The police name used for add security to the controller access
10+
/// </summary>
11+
public const string PolicyName = "JCTools.Shortener.Policy";
12+
13+
/// <summary>
14+
/// String with the collection of allowed characters
15+
/// to be use for the generation fo the short links.
16+
/// The default characters are "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
17+
/// Using the full English alphabet plus all numerals from 0-9 that gives us 62 available characters, meaning we have:
18+
/// (62^2) + (62^3) + (62^4) + (62^5) + (62^6) possible unique tokens which equals: `57 billion 731 million 386 thousand 924´.
19+
/// </summary>
20+
public string ValidCharacters { get; set; } = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
21+
/// <summary>
22+
/// The minimum length (default = 2) of the generated random unique token.
23+
/// </summary>
24+
private int _minLength = 2;
25+
/// <summary>
26+
/// The minimum length (default = 2) of the generated random unique token.
27+
/// </summary>
28+
public int MinLength
29+
{
30+
get => _minLength;
31+
set
32+
{
33+
if (value >= MaxLength)
34+
throw new ArgumentException($"The {nameof(MinLength)} must be less that the {nameof(MaxLength)}. {value} < {MaxLength}?");
35+
else
36+
_minLength = value;
37+
}
38+
}
39+
/// <summary>
40+
/// The maximum length (default = 6) of the generated random unique token.
41+
/// </summary>
42+
private int _maxLength = 6;
43+
/// <summary>
44+
/// The maximum length (default = 6) of the generated random unique token.
45+
/// </summary>
46+
public int MaxLength
47+
{
48+
get => _maxLength;
49+
set
50+
{
51+
if (value <= MinLength)
52+
throw new ArgumentException($"The {nameof(MaxLength)} must be greater that the {nameof(MinLength)}. {value} > {MinLength}?");
53+
else
54+
_maxLength = value;
55+
}
56+
}
57+
/// <summary>
58+
/// Use this property for manage the authorization policy according to you needs.
59+
/// By default, it's configured with the anonymous access, and the redirection url can implement the adequate authorization.
60+
/// </summary>
61+
public Action<AuthorizationPolicyBuilder> ConfigurePolicy { get; set; } = p => p.RequireAssertion(a => true);
62+
}
63+
}

0 commit comments

Comments
 (0)