So I created a plugin, based on the given example by Microsoft on the PLUGIN WALKTHROUGH. It is actually on Create message, so I modified some codes and registered it under Update message.
But when I register it on Update, an exception is thrown that says:
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
So I tried to register it under Create message, and it works.
But I need to register it under update, and I can't find a reliable source on how to change my code so Update can handle it. Here is my code:
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Xrm;
public class Plugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
Entity entity;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "scs_barangayclearanceapplication") { return; }
}
else
{
return;
}
try
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
//var id = (Guid)context.OutputParameters["id"];
SendEmailToContact(service, entity.Id);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
}
private static void SendEmailToContact(IOrganizationService service, Guid id)
{
using (var crm = new XrmServiceContext(service))
{
var testData = crm.scs_barangayclearanceapplicationSet.Where(b => b.Id == id).First();
string sr = testData.scs_ServiceRequestNumber;
var note = new Annotation
{
Subject = "Created with plugin",
NoteText = " Service Request ID = " + id + " Service Request Number = " + sr,
ObjectId = testData.ToEntityReference(),
ObjectTypeCode = testData.LogicalName
};
crm.AddObject(note);
crm.SaveChanges();
}
}
}
I really need your help. Thanks!
Related
I'm trying to write custom exception handling that would write error details into the database.
Right now (in the code I inherited) the DB context is injected into static class with extension method:
Startup.cs:
ExceptionMiddleware.ExceptionMiddlewareConstructor(app.ApplicationServices.GetService<IErrorLoggerService>());
ExceptionMiddleware.cs:
public static class ExceptionMiddleware
{
private static IErrorLoggerService _service;
public static void ExceptionMiddlewareConstructor(IErrorLoggerService service)
{
_service = service;
}
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILog logger)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
if (context != null)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var requestString = "Query: " + context.Request.QueryString.Value;
if (context.Request.Method != "GET")
{
context.Request.Body.Position = 0;
using StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true);
requestString += "Body" + await reader.ReadToEndAsync();
}
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
var claims = context.User.Claims.ToList();
//Filter specific claim
var email = claims.FirstOrDefault(x =>
x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
StringComparison.OrdinalIgnoreCase))?.Value;
if (contextFeature.Error.StackTrace != null)
{
var error = new ERROR
{
//assinging property values for DB entity
};
await _service.CreateAsync(error);
}
}
}
});
});
}
}
ErrorLoggerService is a service that handles db write for ERROR entities, and has DbContext injected into constructor.
Obviously current code is not thread-safe because of the captured dependency to DbContext, but I'm not sure how to inject DbContext into exception handler properly
If you registed your dbcontext as a scoped service by default,
// If you didn't pass the parameter of servicelife time into the method,the lifetime would be scoped by default
builder.Services.AddDbContext<ZipTestContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SomeDbContext") ?? throw new InvalidOperationException("Connection string 'SomeDbContext' not found.")),ServiceLifetime.Scoped);
you could get the dbcontext from container as below:
var dbcontext = context.RequestServices.CreateScope().ServiceProvider.GetService<SomeDbContext>();
And the case related may help:Why cannot resolve DbContext in constructor of custom middleware?
Tried as below:
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
// using static System.Net.Mime.MediaTypeNames;
context.Response.ContentType = Text.Plain;
//var dbcontext = context.RequestServices.GetService<ZipTestContext>();
var dbcontext = context.RequestServices.CreateScope().ServiceProvider.GetService<ZipTestContext>();
var somemodel = new SomeModel() { Description = "Description", Name = "SomeName" };
dbcontext?.Add(somemodel);
dbcontext?.SaveChangesAsync();
await context.Response.WriteAsync("An exception was thrown.");
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not found.");
}
if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});
You could see the different performance between
var dbcontext = context.RequestServices.GetService<ZipTestContext>();
var dbcontext =
And
context.RequestServices.CreateScope().ServiceProvider.GetService<ZipTestContext>();
Result1:
Result2:
I need to use another company's API to query data using POST requests.
The API works (= I receive all the data with no errors) when I query it from the swagger website using the UI, but when I do it from my C# program I get a 500 Internal Server Error.
Where should I be looking for the problem ? Is there a way to get a more detailed error message ?
Edit (added code) :
using System;
using System.Data.Entity;
using System.Data.Entity.Core.Mapping;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.Interception;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Text;
namespace credex_distribution_offers_to_interfaces
{
class Program
{
private const string jsonMediaType = "application/json";
static void Main()
{
FetchJSONAndInsertToDB();
}
private static bool FetchJSONAndInsertToDB()
{
var baseServiceUrl = new Uri("a valid url");
Root rootObject;
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(jsonMediaType));
try
{
string token = FetchToken(httpClient, baseServiceUrl);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token);
}
catch (Exception e)
{
return false;
}
try
{
rootObject = FetchDistributionLookupOffers(httpClient, baseServiceUrl, 29612, 29613, 29614, 29617, 29621);
}
catch (Exception e)
{
return false;
}
}
// database related stuff...
// ...
return true;
}
[DataContract]
public class MortgageForDistributionLookupInputDto
{
public int[] OfferNumbers { get; set; }
}
private static Root FetchDistributionLookupOffers(HttpClient aHttpClient, Uri aBaseServiceUrl, params int[] aOfferNumbers)
{
var input = new MortgageForDistributionLookupInputDto()
{
OfferNumbers = aOfferNumbers
};
var lookup = aHttpClient.PostAsync(new Uri(aBaseServiceUrl, "v1/MortgageDetails/InvestorLookupOffers"), PayloadFor(input)).Result;
if (lookup.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Fetching investor lookup offers failed with HTTP status code '" + lookup.StatusCode + "' : " + lookup.ReasonPhrase + "}");
}
var obj = ValueFor<Root>(lookup.Content);
return obj;
}
private static HttpContent PayloadFor<T>(T aObject)
{
return new StringContent(aObject.SerializeJson(), Encoding.UTF8, jsonMediaType);
}
private static T ValueFor<T>(HttpContent aHttpContent)
{
//var content = aHttpContent.ReadAsStringAsync();
return aHttpContent.ReadAsStreamAsync().Result.DeSerializeJson<T>();
}
private static string FetchToken(HttpClient aHttpClient, Uri aBaseServiceUrl)
{
var login = new LoginRequest()
{
UserName = "some user name",
Password = "some password"
};
var authResult = aHttpClient.PostAsync(new Uri(aBaseServiceUrl, "api/Login"), PayloadFor(login)).Result;
if (authResult.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Fetching authentication token failed. Reason : " + authResult.StatusCode + " -> " + authResult.ReasonPhrase);
}
return authResult.Content.ReadAsStringAsync().Result.Trim('"');
}
}
}
I'm fairly new to .NET and c# and I'm working on a POC where I've run into an issue when a controller throws the error
System.InvalidOperation Exception {"Unable to resolve controller: TenantController"}
The Inner exception details are
No default Instance is registered and cannot be automatically determined for type 'GICS.Web.Managers.Interfaces.ITenantManager'
There is no configuration specified for GICS.Web.Managers.Interfaces.ITenantManager
1.) new TenantController(Default of ITenantManager, Default of IRemedyService)
2.) GICS.Web.Controllers.Api.TenantController
3.) Instance of GICS.Web.Controllers.Api.TenantController
4.) Container.GetInstance(GICS.Web.Controllers.Api.TenantController)
The TenantController looks as follows:
using System.Web.Mvc;
using GICS.Web.Controllers.Api.Abstracts;
using GICS.Web.Managers.Interfaces;
using GICS.Web.Services.Interfaces;
using System.Collections.Generic;
using GICS.Web.ViewModels.Tenant;
using GICS.Web.Models.Tenant;
namespace GICS.Web.Controllers.Api
{
[RoutePrefix("api/tenant")]
public class TenantController : BaseApiController
{
private readonly ITenantManager _tenantsManager;
private readonly IRemedyService _remedyService;
private string token;
public TenantController(ITenantManager tenantsManager, IRemedyService remedyService)
{
_tenantsManager = tenantsManager;
_remedyService = remedyService;
token = null;
}
[HttpGet, Route("{groupId}/{userName}")]
public JsonResult getTenants(string groupId, string UserName)
{
getToken(UserName);
JsonResult result = Json(null);
if (token != null)
{
var tenants = _tenantsManager.GetTenants(token, groupId);
List<TenantViewModel> tenantViewModelList = new List<TenantViewModel>();
foreach (Values x in tenants)
{
TenantViewModel model = new TenantViewModel(x, groupId);
tenantViewModelList.Add(model);
}
result = Json(tenantViewModelList);
}
return result;
}
}
The TenantManager interface is as follows:
using System.Collections.Generic;
using GICS.Web.Models.Tenant;
namespace GICS.Web.Managers.Interfaces
{
public interface ITenantManager
{
IEnumerable<Values> GetTenants(string token, string groupId);
}
}
And the Manager implementation is:
using GICS.Web.Managers.Abstracts;
using GICS.Web.Managers.Interfaces;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using GICS.Web.Models.Tenant;
namespace GICS.Web.Managers
{
public class TentantManager : ManagerBase, ITenantManager
{
public IEnumerable<Models.Tenant.Values> GetTenants(string token, string groupId)
{
Tenant restEntries = null;
List<Models.Tenant.Values> tenantList = new List<Models.Tenant.Values>();
using (WebClient client = new WebClient())
{
client.Headers[HttpRequestHeader.Authorization] = token;
var baseURL = ConfigurationManager.AppSettings["RemedyBaseUrl"];
var apiPath = ConfigurationManager.AppSettings["RemedyAPIPath"];
string getURL = baseURL + apiPath + "ESN%3AAST%3ALogSysComp%5FASTPeople" + "?q=?q=%27PeopleGroup%20Form%20Entry%20ID%27%20%3D%20%22" + groupId + "%22&fields=values(Name)";
string getResponse = client.DownloadString(getURL);
restEntries = JsonConvert.DeserializeObject<Tenant>(getResponse);
foreach (Models.Tenant.Entry x in restEntries.entries)
{
tenantList.Add(x.values);
}
}
return tenantList;
}
}
}
I have other controllers in the project that follow the same approach and all are working except for this one. Anyone spot where I am going wrong here?
Thanks in advance.
I'm currently trying to write a custom filter in my Web API that will allow me to update our database when a user makes a request per their LastActivity time stamp. To do this I need to access their username which is available in the WebHostHttpRequestContext and is viewable when I debug and go down a few layers, but I can't seem to figure out how to access it with my code.
Any idea how I can access this object and get values from it? Here is what I'm working with so far,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Filters;
using MyWebAPI.Models.DAL;
namespace MyWebAPI.App_Service
{
public class MyActionFilter : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var principal = actionExecutedContext.Response.Content;
//using (var db = new databaseContext())
//{
// var dbUser = (from b in db.AspNetUsers
// where b.UserName == principal.Identity.Name
// select b).First();
// dbUser.LastActivity = DateTime.Now;
// db.SaveChanges();
//}
}
}
}
If what you are after is the principal. Try accessing it via the HttpActionExecutedContext.ActionContext.RequestContext.Principal. It should be populated provided the request has be authenticated and a user principal was assign to the request.
namespace MyWebAPI.App_Service {
public class MyActionFilter : System.Web.Http.Filters.ActionFilterAttribute {
public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext) {
var principal = actionExecutedContext.ActionContext.RequestContext.Principal;
if(principal != null) {
using (var db = new databaseContext()) {
var dbUser = (from b in db.AspNetUsers
where b.UserName == principal.Identity.Name
select b).First();
dbUser.LastActivity = DateTime.Now;
db.SaveChanges();
}
}
}
}
}
Although the class is internal, you can treat the WebHostHttpContext object as a dynamic and get it from actionExecutedContext.Request.Properties["MS_RequestContext"].
The following code worked when I needed the original HttpRequestWrapper from my request context (to get POST body data) for example.
dynamic requestContext = actionContext.Request.Properties["MS_RequestContext"];
var requestWrapper = (HttpRequestWrapper)requestContext.Context.Request;
// Cast to (actionContext.RequestContext as WebHostHttpRequestContext).Context
var context = actionContext.RequestContext
.GetType().Assembly.GetType("System.Web.Http.WebHost.WebHostHttpRequestContext")
.GetProperty("Context")
?.GetMethod
.Invoke(actionContext.RequestContext, null) as HttpContextWrapper;
var request = context?.Request;
I have registered a plugin for our quote products. The plugin worked well in our test environment. I have tested it lots of times. Then registered the plugin in the main server. However, the below scenario occurs:
When I create or update the quote products first the quote product form greys out:
After I click on the quote form the error appears. No log files are available (as you see). I debugged the plugin, but there is no error also. After I click the OK, the error disappears and the required business takes place on the quote product (for tax field). Means that the code of plugin has no errors and does its job well. The code is as:
using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Xrm;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk.Deployment;
public class Plugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
Entity entity;
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "quotedetail") { return; }
}
else
{
return;
}
try
{
IOrganizationServiceFactory serviceFactory =
(IOrganizationServiceFactory)serviceProvider.GetService(
typeof(IOrganizationServiceFactory));
IOrganizationService service =
serviceFactory.CreateOrganizationService(context.UserId);
if (context.MessageName == "Create")
{
Entity QuoteProduct = (Entity)context.InputParameters["Target"];
Guid QPID = QuoteProduct.Id;
TaxCreator(service, QPID);
}
if (context.MessageName == "Update" && context.Depth < 3)
{
Entity QuoteProduct = (Entity)context.PostEntityImages["Target"];
Guid QPID = QuoteProduct.Id;
TaxUpdater(service, QPID);
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
}
private static void TaxCreator(IOrganizationService service, Guid QPID)
{
using (var crm = new XrmServiceContext(service))
{
var QuoteProduct = crm.QuoteDetailSet.Where(c => c.QuoteDetailId == QPID).First();
var SaleSetting = crm.new_salessettingSet.Where(c => c.statecode == 0).First();
double TaxPercent = (Convert.ToDouble(SaleSetting.new_TaxPercent) / 100);
if (QuoteProduct.IsPriceOverridden == false)
{
decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
QuoteProduct.Attributes["tax"] = new Money(Tax);
QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit);
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
}
}
}
private static void TaxUpdater(IOrganizationService service, Guid QPID)
{
using (var crm = new XrmServiceContext(service))
{
var QuoteProduct = crm.QuoteDetailSet.Where(c => c.QuoteDetailId == QPID).First();
var SaleSetting = crm.new_salessettingSet.Where(c => c.statecode == 0).First();
double TaxPercent = (Convert.ToDouble(SaleSetting.new_TaxPercent) / 100);
if (QuoteProduct.IsPriceOverridden == false)
{
decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
QuoteProduct.Attributes["tax"] = new Money(Tax);
QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit);
crm.UpdateObject(QuoteProduct);
crm.SaveChanges();
}
}
}
}
I also checked the event viewer on the server for the errors, and no result!
I have registered my plugin on create and update of the quote product.
Any help is greatly appreciated.
I'd go shotgun debug on it. Implement the following tracing. Usually, I place the declaration of the tracer as a class member (so it's visible to all my help methods within the class) and write to it (when desperate and frustrated) after every operation (more or less). Then, when the program crashes by itself (or when I intentionally crash it to see the trace log), I can usually pin-point the problem area.
public class MyPlugin _ IPlugin
{
private ITracingService _trace;
public void Execute(IServiceProvider service)
{
_trace = (ITracingService)service.GetService(typeof(ITracingService));
_trace.Trace("Commencing.");
...
_trace.Trace("Right before an operation. " + someValue);
PerformAnOperation();
_trace.Trace("Right before an other operation. " + someOtherValue);
PerformAnOtherOperation();
...
throw new Exception("Intentional!");
}
}
The, you can follow through to see exactly where the problem originates. In my experience, once one knows where it aches, one can easily suggest how to remedy the issue.
EDIT:
Since the OP requested more details, I'm taking his code and put in the tracing algorithm into it for him. A bit redundant but apparently, CRM can be very confusing. Been there myself.
public class Plugin : IPlugin
{
// Here we declare our tracer.
private ITracingService _trace;
public void Execute(IServiceProvider serviceProvider)
{
// Here we create it.
_trace = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
_trace.Trace("Commencing.");
// Here we crash our plugin just to make sure that we can see the logs
// with trace information. This statement should be gradually moved down
// the code to discover where exactly the plugin brakes.
throw new Exception("Intentional!");
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
Entity entity;
...
try { ... }
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
"An error occurred in the plug-in.", ex);
}
// Here we catch all other kinds of bad things that can happen.
catch(Exception exception) { throw exception; }
}
private static void TaxCreator(IOrganizationService service, Guid QPID)
{
// Here we add a line to the trace hoping that the execution gets here.
_trace.Trace("Entered TaxCreator.");
using (var crm = new XrmServiceContext(service))
{
var QuoteProduct = crm.QuoteDetailSet.Where(...).First();
var SaleSetting = crm.new_salessettingSet.Where(...).First();
double TaxPercent = (Convert.ToDouble(...) / 100);
// Here we add a line to the trace to see if we get past this point.
_trace.Trace("Approaching if statement.");
if (QuoteProduct.IsPriceOverridden == false) { ... }
}
}
private static void TaxUpdater(IOrganizationService service, Guid QPID)
{
// Here we add a line to the trace hoping that the execution gets here.
_trace.Trace("Entered TaxUpdater.");
...
}
}
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
Entity entity;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName != "quotedetail") { return; }
}
else
{
return;
}
try
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
QuoteDetail QuoteProduct = ((Entity)context.InputParameters["Target"]).ToEntity<QuoteDetail>();
if (context.MessageName == "Create" || context.MessageName == "Update")
// && context.Depth < 3) //try to avoid depth unless you have to have it
{
TaxSetter(service, QuoteProduct);
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException( "An error occurred in the plug-in.", ex);
}
}
private static void TaxSetter(IOrganizationService service, QuoteDetail product)
{
using (var crm = new TrainingContext(service))
{
var QuoteProduct = product.ToEntity<QuoteDetail>();
if (QuoteProduct.IsPriceOverridden == false)
{
double TaxPercent = Convert.ToDouble(50 / 100);
decimal Tax = (decimal)Convert.ToDecimal(Convert.ToDouble(QuoteProduct.BaseAmount - QuoteProduct.ManualDiscountAmount.GetValueOrDefault()) * TaxPercent);
decimal PricePerUnit = (decimal)(QuoteProduct.PricePerUnit.GetValueOrDefault() - QuoteProduct.VolumeDiscountAmount.GetValueOrDefault());
QuoteProduct.Tax = Tax; //depending on how you the parameters passed into CrmSvcUtil
QuoteProduct.Attributes["new_result"] = new Money(PricePerUnit); //same deal here
//crm.UpdateObject(QuoteProduct);
//crm.SaveChanges();
//code not needed, the Plugin context will take care of this for you
}
}
}
The idea is, you want your plugin context to work for you; and you want to overtly do as little as possible. I changed the tax calculation because I don't have your attributes; you'll want to change them back. If I've still misread your problem, I'd be happy to delete my post (or try).