I have mocked RewriteContext:
Mock<RewriteContext> rewriteContextMock = new Mock<RewriteContext>();
Then I try to override its HttpContext with a mock:
rewriteContextMock.Setup(x => x.HttpContext.Request).Returns(requestMock.Object);
unfortunately that causes
Unsupported expression: x => x.HttpContext
Non-overridable members (here: RewriteContext.get_HttpContext) may not be used in setup / verification expressions.'
So what I did was
Mock<HttpContext> httpContextMock = new Mock<HttpContext>();
rewriteContextMock.Object.HttpContext = httpContextMock.Object;
But the the question raises: Is there any point in using Setup(), Return() chain when setting up the properites? Have I solved the problem correctly or I should use different approach?
RewriteContext is a simple POCO with no behavior,
/// <summary>
/// A context object for <see cref="RewriteMiddleware"/>
/// </summary>
public class RewriteContext
{
/// <summary>
/// Gets and sets the <see cref="HttpContext"/>
/// </summary>
public HttpContext HttpContext { get; set; }
/// <summary>
/// Gets and sets the File Provider for file and directory checks.
/// </summary>
public IFileProvider StaticFileProvider { get; set; }
/// <summary>
/// Gets and sets the logger
/// </summary>
public ILogger Logger { get; set; } = NullLogger.Instance;
/// <summary>
/// A shared result that is set appropriately by each rule for the next action that
/// should be taken. See <see cref="RuleResult"/>
/// </summary>
public RuleResult Result { get; set; }
internal StringBuilder Builder { get; set; } = new StringBuilder(64);
}
so there is no need to mock it.
All its members can be easily provided as needed to an actual instance of the object.
//...
RewriteContext rewriteContext = new RewriteContext();
var httpContext = new DefaultHttpContext();
httpContext.Request.Scheme = "http";
httpContext.Request.Host = new HostString("localhost");
//...set other members
rewriteContext.HttpContext = httpContext;
//...
Related
I can find quite many resources on how to generate C# models from protobuf messages (it's even built into the Grpc.AspNetCore package), but not the other way around.
I've created a blazor webassembly client/server project similar to this one: https://github.com/stevejgordon/gRPCBasicSample/
I have an Domain layer with lots of C# models that I'm using for the Application. I would like to convert these models into message reply "view models", instead of writing them all in hand (also I'm not even sure of the right conversion between c# and protobuf)
Example:
C# Model:
/// <summary>
/// The area.
/// </summary>
public class Area
{
/// <summary>
/// Initializes a new instance of the <see cref="Area"/> class.
/// </summary>
/// <param name="name">Name of the region.</param>
/// <param name="areaType">What type of area is the region.</param>
/// <param name="numberOfSupplies">number of supplies the region has.</param>
/// <param name="numberOfMusterCrows">number of crowns the region has.</param>
public Area(string name, AreaType areaType, int numberOfSupplies, int numberOfMusterCrows)
{
this.Name = name;
this.AreaType = areaType;
this.NumberOfSupplies = numberOfSupplies;
this.NumberOfMusterCrowns = numberOfMusterCrows;
this.CurrentArmy = new Collection<Unit>();
this.AdjacentAreas = new Collection<Area>();
}
/// <summary>
/// Gets the name of the region.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets or sets which house the area is controlled by.
/// </summary>
public House ControlledBy { get; set; }
/// <summary>
/// Gets the type of area the region is.
/// </summary>
public AreaType AreaType { get; }
/// <summary>
/// Gets the adjacent regions the region has.
/// </summary>
public IEnumerable<Area> AdjacentAreas { get; internal set; }
/// <summary>
/// Gets the number of supplies (barrels) the region has.
/// </summary>
public int NumberOfSupplies { get; }
/// <summary>
/// Gets the number of crowns the region has.
/// </summary>
public int NumberOfMusterCrowns { get; }
/// <summary>
/// Gets or sets a value indicating whether the region is occupied by troops.
/// </summary>
public bool IsOccupied { get; set; }
/// <summary>
/// Gets a value indicating whether the region has a stronghold.
/// </summary>
public bool HasStronghold { get; }
/// <summary>
/// Gets a value indicating whether the region has a castle.
/// </summary>
public bool HasCastle { get; }
/// <summary>
/// Gets or sets the current armies occupying the region.
/// </summary>
public ICollection<Unit> CurrentArmy { get; set; }
/// <summary>
/// Gets or sets the current order token placed.
/// </summary>
public OrderToken? PlacedOrderToken { get; set; }
}
Should turn into something like this (simplified):
message AreaReply {
string name = 1;
enum AreaType {
Land = 0;
Water = 1;
}
AreaType areaType = 4;
House controlledBy = 5;
}
message House {
google.protobuf.StringValue name = 1;
}
Is there really no such generator out there? :)
Thanks
Use this annotation on top of your class definition:
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
See: Protobuf-net serialization without annotation
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.
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.
I'm trying to unit test a repository, but what happen is that when I test it I don't get a 100% coverage rather I get a 0% code coverage on that specific method.
I want to test without using third party frameworks, that's why I wanted to use shims and fakes.
Here is the class that I'm trying to test:
namespace AbstractFactory.Repository
{
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
/// <summary>
/// This class serves as the structure of the Author repository using a database
/// </summary>
public class DbAuthorRepository : IRepository<Author>
{
/// <summary>
/// The context
/// </summary>
private AbstractFactoryPatternEntities context;
/// <summary>
/// Initializes a new instance of the <see cref="DbAuthorRepository"/> class.
/// </summary>
/// <param name="context">The context.</param>
public DbAuthorRepository(AbstractFactoryPatternEntities context)
{
this.context = context;
}
/// <summary>
/// Adds the specified author.
/// </summary>
/// <param name="author">The author.</param>
public void Add(Author author)
{
this.context.Authors.Add(author);
}
/// <summary>
/// Removes the specified author.
/// </summary>
/// <param name="author">The author.</param>
public void Remove(Author author)
{
this.context.Authors.Remove(author);
}
/// <summary>
/// Saves this instance.
/// </summary>
public void Save()
{
this.context.SaveChanges();
}
/// <summary>
/// Gets all.
/// </summary>
/// <returns>
/// returns a list of all the authors
/// </returns>
public IEnumerable<Author> GetAll()
{
List<Author> result = this.context.Authors.ToList();
return result;
}
/// <summary>
/// Gets the by id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>
/// returns an entity
/// </returns>
public Author GetById(int id)
{
Author result = this.context.Authors.Find(id);
////this.context.Authors.Single(a => a.AuthorId == id);
return result;
}
}
}
Here is the test class:
[TestClass]
public class DbAuthorRepositoryTest
{
private ShimAbstractFactoryPatternEntities shimContext;
private ShimDbSet<Author> shimAuthors;
private List<Author> listAuthors;
private DbAuthorRepository repository;
private void SetupShims()
{
this.shimContext = new ShimAbstractFactoryPatternEntities(new AbstractFactoryPatternEntities());
this.listAuthors = new List<Author>
{
new Author { AuthorId = 2, FirstName = "Test2", LastName = "Test2" },
new Author { AuthorId = 3, FirstName = "Test3", LastName = "Test3" },
new Author { AuthorId = 4, FirstName = "Test4", LastName = "Test4" }
};
this.shimAuthors = new ShimDbSet<Author>();
this.shimAuthors.Bind(this.listAuthors.AsQueryable());
ShimAbstractFactoryPatternEntities.AllInstances.AuthorsGet = (a) => { return shimAuthors; };
ShimDbAuthorRepository.AllInstances.GetAll = (a) => { return this.shimContext.Instance.Authors.ToList(); };
//return this.shimContext.Instance.Authors.ToList();
//return this.shimAuthors.Instance.ToList() as IEnumerable<Author>;
//ShimDbSet<Author>.AllInstances.FindObjectArray = (a, b) => { a.ToList(); return shimAuthors.Instance.Contains(b) ; };
}
private void SetupRepository()
{
this.SetupShims();
this.repository = new DbAuthorRepository(new AbstractFactoryPatternEntities());
}
Here is the test method:
[TestMethod]
public void GetAll_MethodIsCalled_RepositoryReturnsCorrectNumberOfAuthorsInTheDbSet()
{
using(ShimsContext.Create())
{
this.SetupRepository();
var authorsCount = this.repository.GetAll().Count();
Assert.AreEqual(authorsCount,3);
}
}
I'm getting 0% code coverage on the GetAll method, how do I get it to 100% and why is it getting 0% ?
Of course you are, your Shim has replaced the functionality completely, thus you're not seeing coverage. Since you're already making sure that context.Authors is overridden, why are you still overriding the GetAll method, which doesn't depend on anything you haven't already overridden?
Here's the situation:
/// <summary>
/// A business logic class.
/// </summary>
public class BusinessClassWithInterceptor : BusinessClass, IBusinessClass
{
/// <summary>
/// Initializes a new instance of the <see cref="BusinessClassWithoutInterceptor"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public BusinessClassWithInterceptor(Logger logger)
: base(logger)
{
}
/// <summary>
/// Displays all cows.
/// </summary>
public void DisplayAllCows()
{
this.Logger.Write("Displaying all cows:");
var repository = new CowRepository();
foreach (CowEntity cow in repository.GetAllCows())
{
this.Logger.Write(" " + cow);
}
}
/// <summary>
/// Inserts a normande.
/// </summary>
public void InsertNormande(int id, string name)
{
this.DisplayAllCows();
var repository = new CowRepository();
repository.InsertCow(new CowEntity { Id = id, Name = name, Origin = CowOrigin.Normandie });
}
}
With castle windsor, this class is configured to be intercepted with this interceptor:
/// <summary>
/// Interceptor for logging business methods.
/// </summary>
public class BusinessLogInterceptor : IInterceptor
{
/// <summary>
/// Intercepts the specified invocation.
/// </summary>
/// <param name="invocation">The invocation.</param>
public void Intercept(IInvocation invocation)
{
Logger logger = ((IBusinessClass)invocation.InvocationTarget).Logger;
var parameters = new StringBuilder();
ParameterInfo[] methodParameters = invocation.Method.GetParameters();
for (int index = 0; index < methodParameters.Length; index++)
{
parameters.AppendFormat("{0} = {1}", methodParameters[index].Name, invocation.Arguments[index]);
if (index < methodParameters.Length - 1)
{
parameters.Append(", ");
}
}
logger.Format("Calling {0}( {1} )", invocation.Method.Name, parameters.ToString());
invocation.Proceed();
logger.Format("Exiting {0}", invocation.Method.Name);
}
}
The issue takes place during the call to InsertNormande.
The call to InsertNormande is well intercepted, but the call to DisplayAllCows in InsertNormande is not intercepted...
It really bothers me.
Is there a way to achieve interception in this scenario ?
I don't think there's an easy way of doing it... method calls that are internal to the class can't be intercepted, since they don't go through the proxy.
You could achieve logging of all methods by other means, such as an AOP framework like PostSharp