I'm looking for a way to automatically match the signature of a method between an interface, an abstract class and a derived class. It's really painful when I change one of the comments, to manually update it 3 times, because I have to update it in the interface, the abstract class and the derived class.. Is there a VS extension or something that could help me do that automatically? Btw, I'm using ReSharper Ultimate. Not sure if it has a such extension.
public interface IBotClient
{
/// <summary>
/// Gets account balance for an asset.
/// </summary>
/// <param name="asset">The asset.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balance.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
Task<Balance> GetBalanceAsync(string asset, CancellationToken ct = default);
/// <summary>
/// Gets account balances.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balances.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
Task<IList<Balance>> GetBalancesAsync(CancellationToken ct = default);
...
}
public abstract class BotClientBase : IBotClient
{
private readonly IBinanceClient _client;
private readonly IBinanceSocketClient _socketClient;
protected BotClientBase(IBinanceClient client, IBinanceSocketClient socketClient)
{
_client = client;
_socketClient = socketClient;
}
/// <summary>
/// Gets account balance for an asset.
/// </summary>
/// <param name="asset">The asset.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balance.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
public abstract Task<Balance> GetBalanceAsync(string asset, CancellationToken ct = default);
/// <summary>
/// Gets account balances.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balances.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
public abstract Task<IList<Balance>> GetBalancesAsync(CancellationToken ct = default);
...
}
public class SpotBotClient : BotClientBase
{
private readonly ILogger<SpotBotClient> _logger;
private readonly IBinanceClient _client;
private readonly IBinanceSocketClient _socketClient;
public SpotBotClient(ILogger<SpotBotClient> logger, IBinanceClient client, IBinanceSocketClient socketClient) : base(client, socketClient)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_client = client;
_socketClient = socketClient;
}
/// <summary>
/// Gets account balance for an asset.
/// </summary>
/// <param name="asset">The asset.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balance.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
public override async Task<Balance> GetBalanceAsync(string asset, CancellationToken ct = default)
{
...
}
/// <summary>
/// Gets account balances.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balances.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
public override async Task<IList<Balance>> GetBalancesAsync(CancellationToken ct = default)
{
...
}
...
}
Documentation comments in C# allows you to use the <inheritdoc> tag, to automatically let derived types reuse the documentation comments from higher up in the hierarchy:
public interface IBotClient
{
/// <summary>
/// Gets account balance for an asset.
/// </summary>
/// <param name="asset">The asset.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balance.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
Task<Balance> GetBalanceAsync(string asset, CancellationToken ct = default);
/// <summary>
/// Gets account balances.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>The account balances.</returns>
/// <exception cref="T:ElonMuskBot.Core.Exceptions.CallFailedException">If the call fails.</exception>
Task<IList<Balance>> GetBalancesAsync(CancellationToken ct = default);
...
}
///<inheritdoc cref="IBotClient"/>
public abstract class BotClientBase : IBotClient
{
}
///<inheritdoc/>
public class SpotBotClient : BotClientBase
{
}
I found out that ReSharper had it built-in.
Alt+Enter on the overriden method and then "Copy documentation from base".
Related
I'm trying to implement the following API Endpoint. Due to the fact, System.Text.Json is now preferred over Newtonsoft.Json, I decided to try it. The response clearly works, but the deserialization doesn't.
Response
https://pastebin.com/VhDw5Rsg (Pastebin because it exceeds the limits)
Issue
I pasted the response into an online converter and it used to work for a bit, but then it broke again once I put the comments.
How do I fix it? I would also like to throw an exception if it fails to deserialize it.
Snippet
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Ardalis.GuardClauses;
using RestSharp;
namespace QSGEngine.Web.Platforms.Binance;
/// <summary>
/// Binance REST API implementation.
/// </summary>
internal class BinanceRestApiClient : IDisposable
{
/// <summary>
/// The base point url.
/// </summary>
private const string BasePointUrl = "https://api.binance.com";
/// <summary>
/// The key header.
/// </summary>
private const string KeyHeader = "X-MBX-APIKEY";
/// <summary>
/// REST Client.
/// </summary>
private readonly IRestClient _restClient = new RestClient(BasePointUrl);
/// <summary>
/// Initializes a new instance of the <see cref="BinanceRestApiClient"/> class.
/// </summary>
/// <param name="apiKey">Binance API key.</param>
/// <param name="apiSecret">Binance Secret key.</param>
public BinanceRestApiClient(string apiKey, string apiSecret)
{
Guard.Against.NullOrWhiteSpace(apiKey, nameof(apiKey));
Guard.Against.NullOrWhiteSpace(apiSecret, nameof(apiSecret));
ApiKey = apiKey;
ApiSecret = apiSecret;
}
/// <summary>
/// The API key.
/// </summary>
public string ApiKey { get; }
/// <summary>
/// The secret key.
/// </summary>
public string ApiSecret { get; }
/// <summary>
/// Gets the total account cash balance for specified account type.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public AccountInformation? GetBalances()
{
var queryString = $"timestamp={GetNonce()}";
var endpoint = $"/api/v3/account?{queryString}&signature={AuthenticationToken(queryString)}";
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"{nameof(BinanceRestApiClient)}: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
var deserialize = JsonSerializer.Deserialize<AccountInformation>(response.Content);
return deserialize;
}
/// <summary>
/// If an IP address exceeds a certain number of requests per minute
/// HTTP 429 return code is used when breaking a request rate limit.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private IRestResponse ExecuteRestRequest(IRestRequest request)
{
const int maxAttempts = 10;
var attempts = 0;
IRestResponse response;
do
{
// TODO: RateLimiter
//if (!_restRateLimiter.WaitToProceed(TimeSpan.Zero))
//{
// Log.Trace("Brokerage.OnMessage(): " + new BrokerageMessageEvent(BrokerageMessageType.Warning, "RateLimit",
// "The API request has been rate limited. To avoid this message, please reduce the frequency of API calls."));
// _restRateLimiter.WaitToProceed();
//}
response = _restClient.Execute(request);
// 429 status code: Too Many Requests
} while (++attempts < maxAttempts && (int)response.StatusCode == 429);
return response;
}
/// <summary>
/// Timestamp in milliseconds.
/// </summary>
/// <returns>The current timestamp in milliseconds.</returns>
private long GetNonce()
{
return DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
/// <summary>
/// Creates a signature for signed endpoints.
/// </summary>
/// <param name="payload">The body of the request.</param>
/// <returns>A token representing the request params.</returns>
private string AuthenticationToken(string payload)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(ApiSecret));
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return BitConverter.ToString(computedHash).Replace("-", "").ToLowerInvariant();
}
/// <summary>
/// The standard dispose destructor.
/// </summary>
~BinanceRestApiClient() => Dispose(false);
/// <summary>
/// Returns true if it is already disposed.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">If this method is called by a user's code.</param>
private void Dispose(bool disposing)
{
if (IsDisposed) return;
if (disposing)
{
}
IsDisposed = true;
}
/// <summary>
/// Throw if disposed.
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
private void ThrowIfDisposed()
{
if (IsDisposed)
{
throw new ObjectDisposedException("BinanceRestClient has been disposed.");
}
}
}
namespace QSGEngine.Web.Platforms.Binance;
/// <summary>
/// Information about the account.
/// </summary>
public class AccountInformation
{
/// <summary>
/// Commission percentage to pay when making trades.
/// </summary>
public decimal MakerCommission { get; set; }
/// <summary>
/// Commission percentage to pay when taking trades.
/// </summary>
public decimal TakerCommission { get; set; }
/// <summary>
/// Commission percentage to buy when buying.
/// </summary>
public decimal BuyerCommission { get; set; }
/// <summary>
/// Commission percentage to buy when selling.
/// </summary>
public decimal SellerCommission { get; set; }
/// <summary>
/// Boolean indicating if this account can trade.
/// </summary>
public bool CanTrade { get; set; }
/// <summary>
/// Boolean indicating if this account can withdraw.
/// </summary>
public bool CanWithdraw { get; set; }
/// <summary>
/// Boolean indicating if this account can deposit.
/// </summary>
public bool CanDeposit { get; set; }
/// <summary>
/// The time of the update.
/// </summary>
//[JsonConverter(typeof(TimestampConverter))]
public long UpdateTime { get; set; }
/// <summary>
/// The type of the account.
/// </summary>
public string AccountType { get; set; }
/// <summary>
/// List of assets with their current balances.
/// </summary>
public IEnumerable<Balance> Balances { get; set; }
/// <summary>
/// Permission types.
/// </summary>
public IEnumerable<string> Permissions { get; set; }
}
/// <summary>
/// Information about an asset balance.
/// </summary>
public class Balance
{
/// <summary>
/// The asset this balance is for.
/// </summary>
public string Asset { get; set; }
/// <summary>
/// The amount that isn't locked in a trade.
/// </summary>
public decimal Free { get; set; }
/// <summary>
/// The amount that is currently locked in a trade.
/// </summary>
public decimal Locked { get; set; }
/// <summary>
/// The total balance of this asset (Free + Locked).
/// </summary>
public decimal Total => Free + Locked;
}
System.Text.Json is implemented quite different compared to Newtonsoft.Json. It is written to be first and foremost a very fast (de)serializer, and try to be allocation-free as much as possible.
However, it also comes with its own set of limitations, and one of those limitations is that out of the box it's a bit more rigid in what it supports.
Let's look at your JSON:
{"makerCommission":10,"takerCommission":10,"buyerCommission":0,
"sellerCommission":0,"canTrade":true,"canWithdraw":true,"canDeposit":true,
"updateTime":1636983729026,"accountType":"SPOT",
"balances":[{"asset":"BTC","free":"0.00000000","locked":"0.00000000"},
{"asset":"LTC","free":"0.00000000","locked":"0.00000000"},
(reformatted and cut for example purposes)
There's two issues here that needs to be resolved:
The properties in the JSON is written with a lowercase first letter. This will simply not match the properties in your .NET Types out of the box.
The values for free and locked are strings in the JSON but typed as decimal in your .NET types.
To fix these, your deserialization code needs to tell System.Text.Json how to deal with them, and this is how:
var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
And then you pass this object in through the deserialization method, like this:
… = JsonSerializer.Deserialize<AccountInformation>(response.Content, options);
This should properly deserialize this content into your objects.
2 parts to this question.
I am the owner of an API and if there is no data to return (based on business rules) the response is delivered like so:
var resp = new { error = "", code = (int)HttpStatusCode.OK, data = leads};
var json = JsonConvert.SerializeObject(resp);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
return Ok(json);
And when I call this in Postman, this is rendered as:
"{\"error\":\"\",\"code\":200,\"data\":[]}"
What is with the slashes?
My second part, which may or may not be fixed by fixing the slashes, is when I consume the API, and deserialize the response to an object, I receive the following error
Error converting value "{"error":"","code":200,"data":[]}" to type 'DataTypes.IntegrationResponse'. Path '', line 1, position 43.
For those who need it, IntegrationResponse is:
public class IntegrationResponse
{
public string error { get; set; }
public int code { get; set; }
public List<IntegrationLead> data { get; set; }
}
I'd make this a comment if I could, need more rep. That said -
Try making List<IntegrationLead> an InegrationLead[] and all the slashes are to escape the quotes and you have an awesome name. Cheers!
Here is how i will do this
in APi
public ActionResult SomeActionMethod() {
return Json(new {foo="bar", baz="Blech"});
}
If you want to use JsonConverter and control how the data get serilized then
public class JsonNetResult : ActionResult
{
/// <summary>
/// Initializes a new instance of the <see cref="JsonNetResult"/> class.
/// </summary>
public JsonNetResult()
{
}
/// <summary>
/// Gets or sets the content encoding.
/// </summary>
/// <value>The content encoding.</value>
public Encoding ContentEncoding { get; set; }
/// <summary>
/// Gets or sets the type of the content.
/// </summary>
/// <value>The type of the content.</value>
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>The data object.</value>
public object Data { get; set; }
/// <summary>
/// Enables processing of the result of an action method by a custom type that inherits from the <see cref="T:System.Web.Mvc.ActionResult"/> class.
/// </summary>
/// <param name="context">The context in which the result is executed. The context information includes the controller, HTTP content, request context, and route data.</param>
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrWhiteSpace(this.ContentType) ? this.ContentType : "application/json";
if (this.ContentEncoding != null)
{
response.ContentEncoding = this.ContentEncoding;
}
if (this.Data != null)
{
response.Write(JsonConvert.SerializeObject(this.Data));
}
}
}
Blockquote
public class CallbackJsonResult : JsonNetResult
{
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
public CallbackJsonResult(HttpStatusCode statusCode)
{
this.Initialize(statusCode, null, null);
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="description">The description.</param>
public CallbackJsonResult(HttpStatusCode statusCode, string description)
{
this.Initialize(statusCode, description, null);
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="data">The callback result data.</param>
public CallbackJsonResult(object data, HttpStatusCode statusCode = HttpStatusCode.OK)
{
this.ContentType = null;
this.Initialize(statusCode, null, data);
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="description">The description.</param>
/// <param name="data">The callback result data.</param>
public CallbackJsonResult(HttpStatusCode statusCode, string description, object data)
{
this.Initialize(statusCode, description, data);
}
/// <summary>
/// Initializes this instance.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="description">The description.</param>
/// <param name="data">The callback result data.</param>
private void Initialize(HttpStatusCode statusCode, string description, object data)
{
Data = new JsonData() { Success = statusCode == HttpStatusCode.OK, Status = (int)statusCode, Description = description, Data = data };
}
}
}
then create an extention
/// <summary>
/// return Json Action Result
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <param name="JsonFormatting"></param>
/// <returns></returns>
public static CallbackJsonResult ViewResult<T>(this T o)
{
return new CallbackJsonResult(o);
}
No APi simple use the extention that you created
public ActionResult SomeActionMethod() {
return new { error = "", code = (int)HttpStatusCode.OK, data = leads}.ViewResult();
}
I have the following class CacheHelper that I use throughout my application that has multiple libraries. The Caching works fine when I test it on my local, but when it is deployed to a webserver, it seems to not work (GET returns null). Any ideas?
public class CacheHelper
{
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object GetValue(string key)
{
var memoryCache = MemoryCache.Default;
return memoryCache.Get(key);
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="absExpiration"></param>
/// <returns></returns>
public bool Add(string key, object value, DateTimeOffset absExpiration)
{
var memoryCache = MemoryCache.Default;
return memoryCache.Add(key, value, absExpiration);
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
public void Delete(string key)
{
var memoryCache = MemoryCache.Default;
if (memoryCache.Contains(key)) memoryCache.Remove(key);
}
}
Usage throughout my application projects:
var cacheHelper = new CacheHelper();
var cachedValue = cacheHelper.GetValue(keyName);
Here is how I set it:
cacheHelper.Add(keyName, cacheValue, DateTimeOffset.UtcNow.AddMinutes(30));
If you are using it in the controller, you are most likely using a new instance on very request.
I am new to Mock testing and i have created a Unit Test using RhinoMock.
First stub is running proper but second is throwing Null Exception, code is as below.
[TestMethod]
public void TestMethod1()
{
SetUp(); //bind List ftsController and ftsEreceiptNumberPrefix
MockRepository mocks = new MockRepository();
var ftsUnitOfWork = mocks.StrictMock<IFtsUnitOfWork>();
ftsUnitOfWork.Stub(x => x.Repository<FtsController>().Get()).Return(ftsController.AsQueryable());
ftsUnitOfWork.Stub(x =>
{
if (x.Repository<FtsPrefix>() == null)
{
throw new ArgumentNullException("Input cannot be null");
}
x.Repository<FtsPrefix>().Get();
}).Return(ftsPrefix.AsQueryable());
;
BoGeneratePrefix generateEreceiptPrefix = new BoGeneratePrefix (logger,ftsUnitOfWork);
generatePrefix.ProcessJob(null);
}
ProcessJob :
public class BoGeneratePrefix : IBoDataPurging
{
#region private Variables
private readonly ILogger _logger;
private readonly IUnitOfWork _ftsUnitOfWork;
private readonly string _defaultTraceCategory;
#endregion
#region BoGeneratePrefix Constructor
/// <summary>
/// Initialized class oject BoGeneratePrefix
/// </summary>
/// <param name="logger"></param>
/// <param name="ftsUoW"></param>
public BoGeneratePrefix(ILogger logger, IFtsUnitOfWork ftsUoW)
{
_defaultTraceCategory = GetType().Name;
_logger = logger;
_ftsUnitOfWork = ftsUoW;
}
public void ProcessJob(Configuration hconfiguration)
{
try
{
var lstController = _ftsUnitOfWork.Repository<FtsController>().Get().Select(x => x. Id).Distinct().AsNoTracking();
foreach (var Id in lstController.ToList())
{
var prefix = _ftsUnitOfWork.Repository<FtsPrefix>()
.Get(x => x. Id == Id
&& x.Status == Constants.NotPrefix).Count();
var ignoreForLowerSafePoint =
_ftsUnitOfWork.Repository<ConfigurationParameter>()
.Get(y => y.ParamType == Constants.IgnoreForLowerSafePoint &&
y.ParamName == Id)
.AsNoTracking()
.Select(t => t.ParamValues).SingleOrDefault();
var currentTime = DateTime.ParseExact(DateTime.Now.ToString(Constants.HHmmssTime),
Constants.HHmmssTime,
System.Globalization.CultureInfo.InvariantCulture).TimeOfDay;
if ( !( hconfiguration.StartTime <= currentTime
&& hconfiguration.EndTime >= currentTime))
{
return;
}
}
}
catch (Exception ex)
{
throw;
}
}
repository :
public interface IRepository<TEntity> : IDisposable where TEntity : class
{
/// <summary>
/// Delete the record from the database by accepting the input parameter as id
/// </summary>
/// <param name="id">Parameter id is used to delete record from the database</param>
void Delete(object id);
/// <summary>
/// Delete the enity
/// </summary>
/// <param name="entityToDelete"></param>
void Delete(TEntity entityToDelete);
/// <summary>
/// Get Queryable by passing the Expression
/// </summary>
/// <param name="filter">this is used for filter parameter/Expression of query</param>
/// <param name="orderBy">Passing condition of orderBy for query</param>
/// <param name="includeProperties">Properties to be included in to the query while generating the query on filter/order by</param>
/// <returns>Entity type Queryable i.e. creates query depend upon accepted entity type as input parameter</returns>
#pragma warning disable S2360
IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
#pragma warning restore S2360
/// <summary>
/// Get Queryable by accepting the input parameter as modifiedSinceTimestamp
/// </summary>
/// <param name="modifiedSinceTimestamp"> modifiedSinceTimestamp </param>
/// <returns></returns>
IQueryable<TEntity> GetModifiedSince(long modifiedSinceTimestamp);
/// <summary>
/// Provides information of an entity by accepting the input parameters
/// </summary>
/// <param name="id">it gives the enity on the id condition </param>
/// <returns>Returns Entity </returns>
TEntity GetById(params object[] id);
/// <summary>
/// Inserts all changes made in this context as a Focus business transaction and enlists it for data insertion.
/// </summary>
/// <param name="entity">Entity i.e. object of generic type </param>
void Insert(TEntity entity);
/// <summary>
/// Gives information about which entity to update
/// </summary>
/// <param name="entityToUpdate">information about to update an enity</param>
void Update(TEntity entityToUpdate);
/// <summary>
///
/// </summary>
/// <param name="query"></param>
/// <param name="parameters"></param>
/// <returns></returns>
IEnumerable<TEntity> ExecWithStoreProcedure(string query, params object[] parameters);
/// <summary>
///
/// </summary>
/// <param name="entityToUpdate"></param>
void Detach(TEntity entityToUpdate);
}
UnitofWork -
public class FtsUnitOfWork : IFtsUnitOfWork
{
#region local variables
private readonly ILogger _logger;
private IFtsDbProvider _ftsDbProvider;
private readonly string _defaultTraceCategory;
private DbContext _context;
private Hashtable _repositories;
#endregion local variables
/// <summary>
/// Constructor for Fts Unit Of Work
/// </summary>
/// <param name="logger"></param>
/// <param name="ftsDbProvider"></param>
public FtsUnitOfWork(ILogger logger, IFtsDbProvider ftsDbProvider)
{
_defaultTraceCategory = GetType().Name;
_logger = logger;
_logger.WriteTrace("FtsUnitOfWork: Initializing", _defaultTraceCategory);
_ftsDbProvider = ftsDbProvider;
_context = new FtsDbContext(_logger, "FtsDbStore", _ftsDbProvider.GetFtsDbCompiledModel());
_logger.WriteTrace("FtsUnitOfWork: Initialized", _defaultTraceCategory);
}
/// <summary>
/// Saves all changes made in this context as a Focus business transaction and enlists it for data consolidation.
/// </summary>
/// <returns></returns>
public virtual int Save()
{
try
{
return _context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
throw new Exception("Optimistic_Concurrency_Check");
}
}
/// <summary>
///
/// </summary>
/// <param name="focusDtTransactionId"></param>
/// <returns></returns>
public int Save(Guid focusDtTransactionId)
{
return _context.SaveChanges();
}
/// <summary>
/// Generic Repository this function creates the Repository by accepting the parameter as Entity type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IRepository<T> Repository<T>() where T : class
{
if (_repositories == null)
{
_repositories = new Hashtable();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(Repository<>);
var repositoryInstance =
Activator.CreateInstance(repositoryType
.MakeGenericType(typeof(T)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IRepository<T>)_repositories[type];
}
/// <summary>
///
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <param name="sqlParameters"></param>
/// <param name="spName"></param>
/// <returns></returns>
public List<T1> GetMany<T1>(SqlParameter[] sqlParameters, string spName)
{
return _context.Database.SqlQuery<T1>(spName, sqlParameters).ToList();
}
/// <summary>
///
/// </summary>
/// <param name="isolationLevel"></param>
/// <returns></returns>
public DbContextTransaction GetDbContextTransaction(IsolationLevel isolationLevel)
{
return _context.Database.BeginTransaction(isolationLevel);
}
#region IDisposable Support
private bool _disposedValue; // To detect redundant calls
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
public virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_logger.WriteTrace("FtsUnitOfWork: Disposing", _defaultTraceCategory);
_ftsDbProvider = null;
_repositories = null;
this._context.Dispose();
this._context = null;
_logger.WriteTrace("FtsUnitOfWork: Disposed", _defaultTraceCategory);
}
_disposedValue = true;
}
}
/// <summary>
///
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
If i commented out first stub then second stub work properly otherwise throws below error :
An exception of type 'System.ArgumentNullException' occurred in Fts.Services.Housekeeping.Tests.dll but was not handled in user code
Additional information: Value cannot be null.
i'm trying to bind a model with a IFormFile or IFormFileCollection property to my custom class CommonFile. i have not found so much documentation on internet about it using asp .net core, i tried to follow this link Custom Model Binding in ASP.Net Core 1.0
but it is binding a SimpleType property and i need to bind a complex type. Anyway i tried to make my version of this binding and i've got the following code:
FormFileModelBinderProvider.cs
public class FormFileModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (!context.Metadata.IsComplexType) return null;
var isIEnumerableFormFiles = context.Metadata.ModelType.GetInterfaces().Contains(typeof(IEnumerable<CommonFile>));
var isFormFile = context.Metadata.ModelType.IsAssignableFrom(typeof(CommonFile));
if (!isFormFile && !isIEnumerableFormFiles) return null;
var propertyBinders = context.Metadata.Properties.ToDictionary(property => property,
context.CreateBinder);
return new FormFileModelBinder(propertyBinders);
}
}
FromFileModelBinder.cs
the following code is incomplete because i'm not getting any result with bindingContext.ValueProvider.GetValue(bindingContext.ModelName); while i'm debugging everything is going well until bindingContext.ModelName has no a value and i can't bind my model From httpContext to Strongly typed Models.
public class FormFileModelBinder : IModelBinder
{
private readonly ComplexTypeModelBinder _baseBinder;
public FormFileModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders)
{
_baseBinder = new ComplexTypeModelBinder(propertyBinders);
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return Task.CompletedTask;
}
}
Any suggestions?
After 10 months i found a solution of that i wanted to do.
In Summary: I Want to replace IFormFile IFormFileCollection with my own classes not attached to Asp .Net because my view models are in different project with poco classes. My custom classes are ICommonFile, ICommonFileCollection, IFormFile (not Asp .net core class) and IFormFileCollection.
i will share it here:
ICommonFile.cs
/// <summary>
/// File with common Parameters including bytes
/// </summary>
public interface ICommonFile
{
/// <summary>
/// Stream File
/// </summary>
Stream File { get; }
/// <summary>
/// Name of the file
/// </summary>
string Name { get; }
/// <summary>
/// Gets the file name with extension.
/// </summary>
string FileName { get; }
/// <summary>
/// Gets the file length in bytes.
/// </summary>
long Length { get; }
/// <summary>
/// Copies the contents of the uploaded file to the <paramref name="target"/> stream.
/// </summary>
/// <param name="target">The stream to copy the file contents to.</param>
void CopyTo(Stream target);
/// <summary>
/// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream.
/// </summary>
/// <param name="target">The stream to copy the file contents to.</param>
/// <param name="cancellationToken">Enables cooperative cancellation between threads</param>
Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken));
}
ICommonFileCollection.cs
/// <inheritdoc />
/// <summary>
/// Represents the collection of files.
/// </summary>
public interface ICommonFileCollection : IReadOnlyList<ICommonFile>
{
/// <summary>
/// File Indexer by name
/// </summary>
/// <param name="name">File name index</param>
/// <returns>File with related file name index</returns>
ICommonFile this[string name] { get; }
/// <summary>
/// Gets file by name
/// </summary>
/// <param name="name">file name</param>
/// <returns>File with related file name index</returns>
ICommonFile GetFile(string name);
/// <summary>
/// Gets Files by name
/// </summary>
/// <param name="name"></param>>
/// <returns>Files with related file name index</returns>
IReadOnlyList<ICommonFile> GetFiles(string name);
}
IFormFile.cs
/// <inheritdoc />
/// <summary>
/// File transferred by HttpProtocol, this is an independent
/// Asp.net core interface
/// </summary>
public interface IFormFile : ICommonFile
{
/// <summary>
/// Gets the raw Content-Type header of the uploaded file.
/// </summary>
string ContentType { get; }
/// <summary>
/// Gets the raw Content-Disposition header of the uploaded file.
/// </summary>
string ContentDisposition { get; }
}
IFormFileCollection.cs
/// <summary>
/// File Collection transferred by HttpProtocol, this is an independent
/// Asp.net core implementation
/// </summary>
public interface IFormFileCollection
{
//Use it when you need to implement new features to Form File collection over HttpProtocol
}
I finally created my model binders successfully, i will share it too:
FormFileModelBinderProvider.cs
/// <inheritdoc />
/// <summary>
/// Model Binder Provider, it inspects
/// any model when the request is triggered
/// </summary>
public class FormFileModelBinderProvider : IModelBinderProvider
{
/// <inheritdoc />
/// <summary>
/// Inspects a Model for any CommonFile class or Collection with
/// same class if exist the FormFileModelBinder initiates
/// </summary>
/// <param name="context">Model provider context</param>
/// <returns>a new Instance o FormFileModelBinder if type is found otherwise null</returns>
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (!context.Metadata.IsComplexType) return null;
var isSingleCommonFile = IsSingleCommonFile(context.Metadata.ModelType);
var isCommonFileCollection = IsCommonFileCollection(context.Metadata.ModelType);
if (!isSingleCommonFile && !isCommonFileCollection) return null;
return new FormFileModelBinder();
}
/// <summary>
/// Checks if object type is a CommonFile Collection
/// </summary>
/// <param name="modelType">Context Meta data ModelType</param>
/// <returns>If modelType is a collection of CommonFile returns true otherwise false</returns>
private static bool IsCommonFileCollection(Type modelType)
{
if (typeof(ICommonFileCollection).IsAssignableFrom(modelType))
{
return true;
}
var hasCommonFileArguments = modelType.GetGenericArguments()
.AsParallel().Any(t => typeof(ICommonFile).IsAssignableFrom(t));
if (typeof(IEnumerable).IsAssignableFrom(modelType) && hasCommonFileArguments)
{
return true;
}
if (typeof(IAsyncEnumerable<object>).IsAssignableFrom(modelType) && hasCommonFileArguments)
{
return true;
}
return false;
}
/// <summary>
/// Checks if object type is CommonFile or an implementation of ICommonFile
/// </summary>
/// <param name="modelType"></param>
/// <returns></returns>
private static bool IsSingleCommonFile(Type modelType)
{
if (modelType == typeof(ICommonFile) || modelType.GetInterfaces().Contains(typeof(ICommonFile)))
{
return true;
}
return false;
}
}
FormFileModelBinder.cs
/// <inheritdoc />
/// <summary>
/// Form File Model binder
/// Parses the Form file object type to a commonFile
/// </summary>
public class FormFileModelBinder : IModelBinder
{
/// <summary>
/// Expression to map IFormFile object type to CommonFile
/// </summary>
private readonly Func<Microsoft.AspNetCore.Http.IFormFile, ICommonFile> _expression;
/// <summary>
/// FormFile Model binder constructor
/// </summary>
public FormFileModelBinder()
{
_expression = x => new CommonFile(x.OpenReadStream(), x.Length, x.Name, x.FileName);
}
/// <inheritdoc />
/// <summary>
/// It Binds IFormFile to Common file, getting the file
/// from the binding context
/// </summary>
/// <param name="bindingContext">Http Context</param>
/// <returns>Completed Task</returns>
// TODO: Bind this context to ICommonFile or ICommonFileCollection object
public Task BindModelAsync(ModelBindingContext bindingContext)
{
dynamic model;
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var formFiles = bindingContext.ActionContext.HttpContext.Request.Form.Files;
if (!formFiles.Any()) return Task.CompletedTask;
if (formFiles.Count > 1)
{
model = formFiles.AsParallel().Select(_expression);
}
else
{
model = new FormFileCollection();
model.AddRange(filteredFiles.AsParallel().Select(_expression));
}
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Actually Everything is working good except when i have Nested Models. I share an example of my models I'm using and I'll do some comments with working scenarios and which don't
Test.cs
public class Test
{
//It's Working
public ICommonFileCollection Files { get; set; }
//It's Working
public ICommonFileCollection Files2 { get; set; }
//This is a nested model
public TestExtra TestExtra { get; set; }
}
TestExtra.cs
public class TestExtra
{
//It's not working
public ICommonFileCollection Files { get; set; }
}
Actually when i make a request to my API I've got the following (Screenshot):
I'm sharing a screenshot of my postman request too for clarifying my request is good.
If there is any subjection to make this work with nested model it would be great.
UPDATE
Asp Net Core Model Binder won't bind model with one property only, if you have one property in a class, that property will be null but when you add two or more will bind it. my mistake i had one one property in a nested class. The entire code is correct.