Controller methods should throw proper exceptions (#156)

* Controller methods should throw proper exceptions
This commit is contained in:
Pravakar Garnayak 2019-04-29 12:03:19 -07:00 коммит произвёл GitHub
Родитель c340384d85
Коммит 7af7f324f6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 216 добавлений и 66 удалений

Просмотреть файл

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\ServerlessLibraryAPI\CosmosLibraryStore.cs" Link="CosmosLibraryStore.cs" />
<Compile Include="..\ServerlessLibraryAPI\ILibraryStore.cs" Link="ILibraryStore.cs" />
<Compile Include="..\ServerlessLibraryAPI\Models\LibraryItem.cs" Link="LibraryItem.cs" />
<Compile Include="..\ServerlessLibraryAPI\ServerlessLibrarySettings.cs" Link="ServerlessLibrarySettings.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\ServerlessLibraryAPI\wwwroot\items.json" Link="items.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="2.3.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,135 @@
using System;
using System.Reflection;
using System.IO;
using ServerlessLibrary.Models;
using System.Collections.Generic;
using Newtonsoft.Json;
using ServerlessLibrary;
using Microsoft.WindowsAzure.Storage.Table;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.RetryPolicies;
using System.Threading.Tasks;
using System.Linq;
namespace LibraryStoreMigration
{
class Program
{
const string tableName = "slitemstats";
static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Please enter specify operation to be performed (cosmosdb|stats)");
Console.WriteLine("Please note that connection informations need to be provided as environment variables.");
return;
}
if (args[0].Equals("cosmosdb", StringComparison.OrdinalIgnoreCase))
{
MigrateToCosmosDB();
}
if (args[0].Equals("stats", StringComparison.OrdinalIgnoreCase))
{
AddNewStatsColumns();
}
}
public static void AddNewStatsColumns()
{
TableRequestOptions tableRequestRetry = new TableRequestOptions { RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(2), 3) };
TableQuery<NewSLItemStats> query = new TableQuery<NewSLItemStats>();
TableContinuationToken continuationToken = null;
List<NewSLItemStats> entities = new List<NewSLItemStats>();
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ServerlessLibrarySettings.SLStorageString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference(tableName);
var opContext = new OperationContext();
do
{
TableQuerySegment<NewSLItemStats> queryResults = (table).ExecuteQuerySegmentedAsync(query, continuationToken, tableRequestRetry, opContext).Result;
continuationToken = queryResults.ContinuationToken;
entities.AddRange(queryResults.Results);
} while (continuationToken != null);
Console.WriteLine(entities.Count);
List<LibraryItem> libraryItems = GetAllLibraryItemsFromFile();
foreach (var entity in entities)
{
entity.id = libraryItems.FirstOrDefault(l => l.Template == entity.template).Id;
entity.likes = 0;
entity.dislikes = 0;
TableOperation operation = TableOperation.InsertOrMerge(entity);
Task<TableResult> r = table.ExecuteAsync(operation);
TableResult a = r.Result;
}
}
public static void MigrateToCosmosDB()
{
var libraryItems = GetAllLibraryItemsFromFile();
Console.WriteLine("Number of samples to be migrated from file to cosmos db: {0}", libraryItems.Count);
CosmosLibraryStore libraryStore = new CosmosLibraryStore();
IList<LibraryItem> libraryItemsInCosmos = libraryStore.GetAllItems().Result;
Console.WriteLine("Number of samples already present in cosmos db: {0}", libraryItemsInCosmos.Count);
if (libraryItemsInCosmos.Count != libraryItems.Count)
{
foreach (LibraryItem libraryItem in libraryItems)
{
if (!libraryItemsInCosmos.Any(c => c.Id == libraryItem.Id))
{
Console.WriteLine("Item {0} not present in cosmos db. will be migrated" + libraryItem.Id);
try
{
libraryStore.Add(libraryItem).Wait();
Console.WriteLine("Migrated sample with id {0}" + libraryItem.Id);
}
catch (Exception ex)
{
Console.WriteLine("Got exception {0}", ex);
throw;
}
}
}
Console.WriteLine("Samples are successfully migrated to cosmos db");
}
else
{
Console.WriteLine("Samples are already migrated to cosmos db");
}
}
public static List<LibraryItem> GetAllLibraryItemsFromFile()
{
var assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream("LibraryStoreMigration.items.json"))
using (StreamReader reader = new StreamReader(stream))
{
string result = reader.ReadToEnd();
return JsonConvert.DeserializeObject<List<LibraryItem>>(result);
}
}
}
public class OldSLItemStats : TableEntity
{
public string template { get; set; }
public int totalDownloads { get; set; }
public int downloadsToday { get; set; }
public int downloadsThisWeek { get; set; }
public int downloadsThisMonth { get; set; }
public DateTime lastUpdated { get; set; }
}
public class NewSLItemStats : OldSLItemStats
{
public string id { get; set; }
public int likes { get; set; }
public int dislikes { get; set; }
}
}

Просмотреть файл

@ -73,34 +73,7 @@ namespace ServerlessLibrary
{ {
IList<LibraryItem> libraryItems; IList<LibraryItem> libraryItems;
IList<LibraryItemWithStats> libraryItemsWithStats = new List<LibraryItemWithStats>(); IList<LibraryItemWithStats> libraryItemsWithStats = new List<LibraryItemWithStats>();
libraryItems = await this.libraryStore.GetAllItems();
if (cosmosDBInitialized)
{
libraryItems = await this.libraryStore.GetAllItems();
}
else
{
libraryItems = await new FileLibraryStore(_env).GetAllItems();
if (!string.IsNullOrWhiteSpace(ServerlessLibrarySettings.CosmosEndpoint))
{
IList<LibraryItem> libraryItemsInCosmos = await this.libraryStore.GetAllItems();
if (libraryItemsInCosmos.Count == 0)
{
foreach (LibraryItem libraryItem in libraryItems)
{
this.libraryStore.Add(libraryItem);
}
}
else
{
libraryItems = libraryItemsInCosmos;
}
cosmosDBInitialized = true;
}
}
var stats = await StorageHelper.getSLItemRecordsAsync(); var stats = await StorageHelper.getSLItemRecordsAsync();
foreach (var storeItem in libraryItems) foreach (var storeItem in libraryItems)
{ {

Просмотреть файл

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ServerlessLibrary.Models; using ServerlessLibrary.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Threading.Tasks;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
@ -53,7 +54,7 @@ namespace ServerlessLibrary.Controllers
[HttpPut] [HttpPut]
[ProducesResponseType(typeof(LibraryItem), 200)] [ProducesResponseType(typeof(LibraryItem), 200)]
public IActionResult Put([FromBody]LibraryItem libraryItem) public async Task<IActionResult> Put([FromBody]LibraryItem libraryItem)
{ {
if (!User.Identity.IsAuthenticated) if (!User.Identity.IsAuthenticated)
{ {
@ -74,7 +75,7 @@ namespace ServerlessLibrary.Controllers
GitHubUser user = new GitHubUser(User); GitHubUser user = new GitHubUser(User);
libraryItem.Author = user.UserName; libraryItem.Author = user.UserName;
StorageHelper.submitContributionForApproval(JsonConvert.SerializeObject(libraryItem)); await StorageHelper.submitContributionForApproval(JsonConvert.SerializeObject(libraryItem));
return new JsonResult(libraryItem); return new JsonResult(libraryItem);
} }

Просмотреть файл

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using ServerlessLibrary.Models; using ServerlessLibrary.Models;
using System;
namespace ServerlessLibrary.Controllers namespace ServerlessLibrary.Controllers
{ {
@ -8,12 +10,26 @@ namespace ServerlessLibrary.Controllers
[ApiController] [ApiController]
public class MetricsController : ControllerBase public class MetricsController : ControllerBase
{ {
private ILogger<MetricsController> logger;
public MetricsController(ILogger<MetricsController> logger)
{
this.logger = logger;
}
// PUT api/<controller>/downloads // PUT api/<controller>/downloads
[ProducesResponseType(typeof(bool), 200)] [ProducesResponseType(typeof(bool), 200)]
[HttpPut] [HttpPut]
public JsonResult Downloads([FromBody]string id) public JsonResult Downloads([FromBody]string id)
{ {
StorageHelper.updateUserStats(JsonConvert.SerializeObject(new { id, userAction = "download" })); try
{
StorageHelper.updateUserStats(JsonConvert.SerializeObject(new { id, userAction = "download" })).Wait();
}
catch (Exception ex)
{
logger.LogError("Unable to update download count. Exception: {0}", ex.ToString());
}
return new JsonResult(true); return new JsonResult(true);
} }
// PUT api/<controller>/sentiment // PUT api/<controller>/sentiment
@ -21,20 +37,28 @@ namespace ServerlessLibrary.Controllers
[HttpPut] [HttpPut]
public IActionResult Sentiment([FromBody]SentimentPayload sentimentPayload) public IActionResult Sentiment([FromBody]SentimentPayload sentimentPayload)
{ {
if (sentimentPayload.LikeChanges < -1 if (sentimentPayload.LikeChanges < -1
|| sentimentPayload.LikeChanges > 1 || sentimentPayload.LikeChanges > 1
|| sentimentPayload.LikeChanges == sentimentPayload.DislikeChanges) || sentimentPayload.LikeChanges == sentimentPayload.DislikeChanges)
{ {
return BadRequest("Invalid values for like or dislike count"); return BadRequest("Invalid values for like or dislike count");
} }
StorageHelper.updateUserStats(JsonConvert.SerializeObject(new try
{ {
id = sentimentPayload.Id, StorageHelper.updateUserStats(JsonConvert.SerializeObject(new
userAction = "Sentiment", {
likeChanges = sentimentPayload.LikeChanges, id = sentimentPayload.Id,
dislikeChanges = sentimentPayload.DislikeChanges userAction = "Sentiment",
})); likeChanges = sentimentPayload.LikeChanges,
dislikeChanges = sentimentPayload.DislikeChanges
})).Wait();
}
catch (Exception ex)
{
logger.LogError("Unable to update Sentiments. Exception: {0}", ex.ToString());
}
return new JsonResult(true); return new JsonResult(true);
} }
} }

Просмотреть файл

@ -17,13 +17,10 @@ namespace ServerlessLibrary
{ {
public CosmosLibraryStore() public CosmosLibraryStore()
{ {
if (!string.IsNullOrWhiteSpace(ServerlessLibrarySettings.CosmosEndpoint)) CosmosDBRepository<LibraryItem>.Initialize();
{
CosmosDBRepository<LibraryItem>.Initialize();
}
} }
async public void Add(LibraryItem libraryItem) public async Task Add(LibraryItem libraryItem)
{ {
await CosmosDBRepository<LibraryItem>.CreateItemAsync(libraryItem); await CosmosDBRepository<LibraryItem>.CreateItemAsync(libraryItem);
} }
@ -34,8 +31,7 @@ namespace ServerlessLibrary
return libraryItems.ToList(); return libraryItems.ToList();
} }
} }
/// <summary> /// <summary>
/// Cosmos db APIs /// Cosmos db APIs
/// </summary> /// </summary>

Просмотреть файл

@ -20,7 +20,7 @@ namespace ServerlessLibrary
_env = env; _env = env;
} }
public void Add(LibraryItem libraryItem) public Task Add(LibraryItem libraryItem)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

Просмотреть файл

@ -13,7 +13,7 @@ namespace ServerlessLibrary
/// Add an item to library /// Add an item to library
/// </summary> /// </summary>
/// <param name="libraryItem">Library item </param> /// <param name="libraryItem">Library item </param>
void Add(LibraryItem libraryItem); Task Add(LibraryItem libraryItem);
/// <summary> /// <summary>
/// Get all items from library /// Get all items from library

Просмотреть файл

@ -25,7 +25,6 @@ namespace ServerlessLibrary
public static string CosmosAuthkey { get { return config(); } } public static string CosmosAuthkey { get { return config(); } }
public static string Database { get { return "serverlesslibrary"; } } public static string Database { get { return "serverlesslibrary"; } }
public static string Collection { get { return "contributions"; } } public static string Collection { get { return "contributions"; } }
public static bool ApiOnly { get { return Boolean.Parse(config("false")); } }
} }
} }

Просмотреть файл

@ -1,12 +1,15 @@
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ServerlessLibrary.OAuth.GitHub; using ServerlessLibrary.OAuth.GitHub;
using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.Swagger;
using System.Threading.Tasks;
namespace ServerlessLibrary namespace ServerlessLibrary
{ {
@ -14,7 +17,7 @@ namespace ServerlessLibrary
{ {
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
@ -62,13 +65,10 @@ namespace ServerlessLibrary
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ {
app.UseExceptionHandler("/Error");
app.UseHsts(); app.UseHsts();
app.UseDefaultFiles(); app.UseDefaultFiles();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseSpaStaticFiles(); app.UseSpaStaticFiles();
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Serverless library API v1"); c.SwaggerEndpoint("/swagger/v1/swagger.json", "Serverless library API v1");
@ -84,18 +84,15 @@ namespace ServerlessLibrary
template: "{controller}/{action=Index}/{id?}"); template: "{controller}/{action=Index}/{id?}");
}); });
if (!ServerlessLibrarySettings.ApiOnly) app.UseSpa(spa =>
{ {
app.UseSpa(spa => spa.Options.SourcePath = "ClientApp";
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
spa.UseReactDevelopmentServer(npmScript: "start"); spa.UseReactDevelopmentServer(npmScript: "start");
} }
}); });
}
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {

Просмотреть файл

@ -52,13 +52,13 @@ namespace ServerlessLibrary
return queue; return queue;
} }
public static async void submitContributionForApproval(string contributionPayload) public static async Task submitContributionForApproval(string contributionPayload)
{ {
var message = new CloudQueueMessage(contributionPayload); var message = new CloudQueueMessage(contributionPayload);
await (await getQueueReference(slContributionRequests)).AddMessageAsync(message); await (await getQueueReference(slContributionRequests)).AddMessageAsync(message);
} }
public static async void updateUserStats(string statsPayload) public static async Task updateUserStats(string statsPayload)
{ {
var message = new CloudQueueMessage(statsPayload); var message = new CloudQueueMessage(statsPayload);
await (await getQueueReference(slItemTableName)).AddMessageAsync(message); await (await getQueueReference(slItemTableName)).AddMessageAsync(message);