I had an MVC 3 controller action defined like this:
public ActionResult _UpdateFilter(int a, string b, List<string> c)
that worked great. There's a new requirement to sometimes also send a Dictionary<int, bool> to the action, depending on user action in the UI.
To populate that dictionary, I add hidden fields to the appropriate form like this:
<input type="hidden" id="id-#(something)-Key" name="sometimesSet[#(idx)].Key" value="#(myIntValue)" />
<input type="hidden" id="id-#(something)-Value" name="sometimesSet[#(idx)].Value" value="#(myBoolValue)" />
and I extended my Controller Action like this:
public ActionResult _UpdateFilter(int a, string b, List<string> c, Dictionary<int, bool> sometimesSet = null)
So far, so good. As long as I populate at least one hidden key/value pair in the form. If I do not, my Action is not invoked and I get the exception:
Value cannot be null.
Parameter name: key
(long stack trace at end of question).
The Question
I gather the route mapping can't figure out that sometimesSet is optional, but I don't know how to configure that properly. How do I do that? I have not edited the route definitions in Global.asax.
The stack trace
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
at System.Web.Mvc.ValueProviderUtil.GetKeysFromPrefix(IEnumerable`1 collection, String prefix)
at System.Web.Mvc.DictionaryValueProvider`1.GetKeysFromPrefix(String prefix)
at System.Web.Mvc.ValueProviderCollection.GetKeysFromPrefixFromProvider(IValueProvider provider, String prefix)
at System.Web.Mvc.ValueProviderCollection.<>c__DisplayClass11.<GetKeysFromPrefix>b__c(IValueProvider provider)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at System.Web.Mvc.ValueProviderCollection.GetKeysFromPrefix(String prefix)
at System.Web.Mvc.DefaultModelBinder.UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType)
at System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state)
at System.Web.Mvc.Controller.<>c__DisplayClass1d.<BeginExecuteCore>b__17(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__3(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<BeginProcessRequest>b__2()
at System.Web.Mvc.SecurityUtil.<>c__DisplayClassb`1.<ProcessInApplicationTrust>b__a()
at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust[TResult](Func`1 func)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
I found the cleanest solution is to implement my own ModelBinder that understands how to populate a Dictionary<K,V> even if no relevant values are found on the wire (it will return an empty Dictionary<K,V>.
Here's the code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace My.ModelBinders
{
/// <summary>
/// Binds to a generic Dictionary using the basic format outlined at
/// http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
/// with the relaxation that the indices can be arbitrary integers (need not start at 0 or be sequential).
/// Returns an empty dictionary of no matching parameters found on the wire rather than throwing
/// an Exception as the current default binder does.
/// </summary>
/// <typeparam name="K">Key type</typeparam>
/// <typeparam name="V">Value type</typeparam>
public class OptionalDictionaryBinder<K,V> : CustomBinderBase, IModelBinder
{
/// <summary>
/// Pull key/value pairs out of request. Modified from
/// https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
/// Files not currently supported.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private IEnumerable<KeyValuePair<string, string>> GetValueProviderData(ControllerContext context)
{
foreach (var fk in context.HttpContext.Request.Form.Keys)
{
yield return new KeyValuePair<string, string>((string)fk, context.HttpContext.Request.Form[(string)fk]);
}
foreach (var rd in context.RouteData.Values)
{
yield return new KeyValuePair<string, string>(rd.Key, (string)rd.Value);
}
foreach (var qp in context.HttpContext.Request.QueryString)
{
yield return new KeyValuePair<string, string>((string)qp, context.HttpContext.Request.QueryString[(string)qp]);
}
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
Dictionary<K, V> dict = new Dictionary<K, V>();
Dictionary<int, K> keys = new Dictionary<int, K>();
Dictionary<int, V> values = new Dictionary<int, V>();
foreach (KeyValuePair<string,string> formParam in GetValueProviderData(controllerContext))
{
if (formParam.Key.StartsWith(bindingContext.ModelName, StringComparison.InvariantCultureIgnoreCase))
{
int startbracket = formParam.Key.IndexOf("[");
if (startbracket != bindingContext.ModelName.Length) throw new Exception("Did not find [ directly after model name");
int endbracket = formParam.Key.IndexOf("]", bindingContext.ModelName.Length + 1);
if (endbracket == -1) throw new Exception("Did not find closing bracket in " + formParam);
int idx;
string idxText = formParam.Key.Substring(bindingContext.ModelName.Length + 1, endbracket - bindingContext.ModelName.Length - 1);
if (!int.TryParse(idxText, out idx))
{
throw new Exception("Could not parse numeric index from " + formParam);
}
if (formParam.Key.EndsWith(".Key", StringComparison.InvariantCultureIgnoreCase))
{
if (keys.ContainsKey(idx))
{
throw new Exception("The index " + idx + " was repeated.");
}
K dictKey;
try
{
dictKey = (K)Convert.ChangeType(formParam.Value, typeof(K));
keys.Add(idx, dictKey);
}
catch (NotSupportedException)
{
throw new Exception("The given key '" + formParam.Key + "' could not be converted to type K");
}
}
else if (formParam.Key.EndsWith(".Value", StringComparison.InvariantCultureIgnoreCase))
{
if (values.ContainsKey(idx))
{
throw new Exception("The index " + idx + " was repeated.");
}
V dictValue;
try
{
dictValue = (V)Convert.ChangeType(formParam.Value, typeof(V));
values.Add(idx, dictValue);
}
catch (NotSupportedException)
{
throw new Exception("The given value '" + formParam.Value + "' could not be converted to type V");
}
}
}
}
if (!keys.Keys.SequenceEqual(values.Keys))
{
throw new Exception("Keys and values do not match.");
}
foreach (KeyValuePair<int, K> kvp in keys)
{
dict.Add(kvp.Value, values[kvp.Key]);
}
return dict;
}
}
}
Usage:
public ActionResult _UpdateFilter(int a, string b, List<string> c,
[ModelBinder(typeof(OptionalDictionaryBinder<int, bool>))]
Dictionary<int, bool> sometimesSet)
Related
I created a DTO class that is not mapped to any SQL table in the model:
public class FooDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
I created an entity set of this type in the model:
public override IEdmModel GetModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<FooDTO>("FooDTOs");
// ...
}
And created a custom controller:
public class FooDTOsController : ODataController
{
private IDatasourceFactory datasourceFactory;
public FooDTOsController(IDatasourceFactory datasourceFactory)
{
this.datasourceFactory = datasourceFactory;
}
// The GET is working great.
public IHttpActionResult Get()
{
var db = (MyModel)this.datasourceFactory;
IQueryable<FooDTO> foos = db.Set<Foo>().Select(
t => new FooDTO()
{
Id = t.Id,
Name = "blah blah blah"
});
return Ok(foos);
}
// I am not getting here.
[HttpPost]
public IHttpActionResult Post(FooDTO entity)
{
System.Diagnostics.Debug.WriteLine("YEAH!");
return Ok(entity);
}
}
But the Post method is not being executed. I have tried many ways to define the POST method, but I cannot find one that works.
Posting some data into my controller, results in the following error:
Cannot create an abstract class.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated
in the code.
Exception Details: System.MissingMethodException: Cannot create an abstract class.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception
stack trace below.
Stack Trace:
[MissingMethodException: Cannot create an abstract class.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +138
System.Activator.CreateInstance(Type type, Boolean nonPublic) +105
System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1528
System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +191
System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) +27
System.Web.HttpRuntime.CreateNonPublicInstance(Type type, Object[] args) +82
System.Web.Configuration.HandlerFactoryCache..ctor(String type) +58
System.Web.HttpApplication.GetFactory(String type) +104
System.Web.MaterializeHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +262
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +137
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.114.0
UPDATE: I am using the following PowerShell script to test the POST method:
$body = #{
Id = 1;
Name = "Blah blah blah";
} | ConvertTo-Json -Compress
$response = Invoke-WebRequest `
-Method POST `
-Uri "https://localhost/MyModel/FooDTOs/" `
-ContentType "application/json" `
-Body $body
I am trying to use Convert function from IValueConverter, but I have to call another function in it. I will use his return value but I got that error telling me to return an object value in the converter, any idea how can I avoid this please.
public void Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
RestClient client = new RestClient();
client.BaseUrl = "http://";
RestRequest request = new RestRequest();
request.Method = Method.GET;
request.AddParameter("action", "REE");
request.AddParameter("atm_longitude", location.Longitude);
client.ExecuteAsync(request, ParseFeedCallBack_ListDistance);
}
public void ParseFeedCallBack_ListDistance(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
ParseXMLFeedDistance(response.Content);
}
}
private string ParseXMLFeedDistance(string feed)
{
.... return myvalueToBind;
}
A simple way to calculate the distance between two coordinates, in this case, assuming you have the coordinates of the device,
using System.Device.Location;
public class GeoCalculator
{
public static double Distance(double deviceLongitude, double deviceLatitude, double atmLongitude, double atmLatitude)
{
//Coordinates of ATM (or origin).
var atmCoordinates = new GeoCoordinate(atmLatitude, atmLongitude);
//Coordinates of Device (or destination).
var deviceCordinates = new GeoCoordinate(deviceLatitude, deviceLongitude);
//Distance in meters.
return atmCoordinates.GetDistanceTo(deviceCordinates);
}
}
Hence your converter can look like:
public class DistanceConverter : IValueConverter
{
/// <summary>
/// This is your device coordinate.
/// </summary>
private static GeoCoordinate devCoordinate = new GeoCoordinate(61.1631, -149.9721);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var location = value as LocationModel;
if (location != null)
{
return GeoCalculator.Distance(devCoordinate.Longitude, devCoordinate.Latitude, location.Longitude, location.Latitude);
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Keep in mind that I would personally not use a converter for this. I would simply expose a simple property in my model that does this calculation as it's a simple logic. If you happen to be a purist and don't like any logic in your model, looping through the list and setting a property on your model would work too.
I am having issues publishing generic messages for derived types and having the handler invoked using MassTransit v2.8.0.
If I publish a message of type HtmlBlockNewMessage, the consumer is never invoked. If I publish a ServiceBusMessage object and change the consumer to Consumes<ServiceBusMessage>.Context, the consumer is invoked.
The code fails for a derived type. It only works for the parent type (ServiceBusMessage).
Types:
[Serializable]
public class ServiceBusMessage
{
}
[Serializable]
public class ServiceBusResponse
{
public int ResultCode { get; set; }
}
// Request
[Serializable]
public class ContentItemMessage : ServiceBusMessage
{
public string SiteId { get; set; }
public string PageId { get; set; }
public string ContentItemId { get; set; }
}
[Serializable]
public class HtmlBlockNewMessage : ContentItemMessage
{
public string HtmlData { get; set; }
}
// Response
[Serializable]
public class ContentItemMessageResponse : ServiceBusResponse
{
public string Name { get; set; }
public string ItemType { get; set; }
public string ItemHandler { get; set; }
}
[Serializable]
public class HtmlBlockNewMessageResponse : ContentItemMessageResponse
{
public string DataId { get; set; }
}
Consumer:
public class HtmlBlockConsumer : Consumes<HtmlBlockNewMessage>.Context
{
private IHtmlDataService htmlDataService;
public static ILogger Logger { get; set; }
public HtmlBlockConsumer()
: this(null)
{
}
public HtmlBlockConsumer(IHtmlDataService htmlDataService)
{
Logger = Log4NetLogger.GetLogger();
this.htmlDataService = htmlDataService ?? IoC.Container.Resolve<IHtmlDataService>();
}
public void Consume(IConsumeContext<HtmlBlockNewMessage> message)
{
// Do some stuff
message.Respond(new HtmlBlockNewMessageResponse() { ResultCode = 1 } );
}
}
Bus registration from publisher side:
var bus = ServiceBusFactory.New(sbc =>
{
sbc.EnableMessageTracing();
sbc.UseMsmq();
sbc.VerifyMsmqConfiguration();
sbc.UseMulticastSubscriptionClient();
sbc.SetNetwork("Test");
sbc.UseXmlSerializer();
sbc.UseControlBus();
sbc.ReceiveFrom("msmq://localhost/AuctionCMS.Web.Publisher");
MtServiceBus.ValidateBus(sbc);
});
IoC.Container.RegisterInstance(bus);
Bus registration from consumer side:
var bus = ServiceBusFactory.New(sbc =>
{
sbc.EnableMessageTracing();
sbc.UseMsmq();
sbc.VerifyMsmqConfiguration();
sbc.UseMulticastSubscriptionClient();
sbc.SetNetwork("Test");
sbc.UseXmlSerializer();
sbc.UseControlBus();
sbc.ReceiveFrom("msmq://localhost/AuctionCMS.Consumer");
sbc.Subscribe(subs =>
{
// These are being manually registered due to some issues getting
// StructureMap to scan my assemblies
subs.Instance(new HtmlBlockConsumer());
subs.Instance(new BrowserConsumer());
subs.Instance(new OfferConsumer());
});
});
IoC.Container.RegisterInstance(bus);
My publish extension:
public static TR Publish<T, TR>(this IServiceBus bus, T message) where T : ServiceBusMessage where TR : ServiceBusResponse
{
TR response = null;
IoC.Container.Resolve<IServiceBus>().PublishRequest(message, callback =>
{
callback.SetTimeout(10.Seconds());
try
{
callback.Handle<TR>(m =>
{
response = m; /
});
}
catch (Exception ex)
{
throw;
}
});
return response;
}
Calling code:
// First I create a message specific to the type of action I am performing
var message = new HtmlBlockNewMessage() { HtmlData = "Hello" };
// Then I call a function which accepts a ContentItemMessage and calls Publish
public void AddContentItem(ContentItemMessage message)
{
// Do some preprocessing
// This call times out
var response = this.auctionCmsServices.Bus.Publish<ContentItemMessage,
ContentItemMessageResponse>(message);
// Do some more processing
}
This is the exception
[RequestTimeoutException: Timeout waiting for response, RequestId: 54910000-307f-20cf-c0c2-08d06b31cf6f]
MassTransit.RequestResponse.RequestImpl`1.Wait() in d:\BuildAgent-03\work\aa063b4295dfc097\src\MassTransit\RequestResponse\RequestImpl.cs:124
MassTransit.RequestResponseExtensions.PublishRequest(IServiceBus bus, TRequest message, Action`1 configureCallback) in d:\BuildAgent-03\work\aa063b4295dfc097\src\MassTransit\RequestResponseExtensions.cs:31
AuctionCMS.Framework.ServiceBus.MtServiceBus.Publish(IServiceBus bus, T message) in c:\Users\rick\Documents\Visual Studio 2012\Projects\AuctionCMS\AuctionCMS.Framework\ServiceBus\MtServiceBus.cs:24
AuctionCMS.Framework.Entity.Page.AddContentItem(ISite site, String zone, Int32 location, ContentItemMessage message) in c:\Users\rick\Documents\Visual Studio 2012\Projects\AuctionCMS\AuctionCMS.Framework\Entity\Page.cs:48
AuctionCMS.Framework.Entity.Site.SetDefaultContent() in c:\Users\rick\Documents\Visual Studio 2012\Projects\AuctionCMS\AuctionCMS.Framework\Entity\Site.cs:117
AuctionCMS.Web.Controllers.AdminSitesController.NewSite(SiteNewModel model, HttpPostedFileBase file) in c:\Users\rick\Documents\Visual Studio 2012\Projects\AuctionCMS\AuctionCMS.Web\Controllers\AdminSitesController.cs:69
lambda_method(Closure , ControllerBase , Object[] ) +179
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +261
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +39
System.Web.Mvc.Async.<>c__DisplayClass42.<BeginInvokeSynchronousActionMethod>b__41() +34
System.Web.Mvc.Async.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33() +124
System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +838059
System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +838059
System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +838059
System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +838059
System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult) +15
System.Web.Mvc.Async.<>c__DisplayClass2a.<BeginInvokeAction>b__20() +33
System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult) +838644
System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult) +28
System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +15
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +65
System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +15
System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +51
System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +42
System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +15
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +51
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +606
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288
Edit:
I went with a generic approach to solve this. It's a but ugly from the caller's perspective, but it works.
public TR AddContentItem<T, TR>(T message) where T : ContentItemMessage where TR : ContentItemMessageResponse
{
var response = this.auctionCmsServices.Bus.Publish<T, TR>(message);
return response;
}
The calling code now looks like this:
page.AddContentItem(new HtmlBlockNewMessage() { HtmlData =
"This is some html" });
Eugene's comment is correct. What's happening here is you are publishing a message of type ContentItemMessage. A consumer of HtmlBlockNewMessage will not executed since the message is published as a ContentItemMessage and a ServiceBusMessage. MassTransit message mis-typing is one of a number of things out there on how this works.
Your options:
Change AddContentItem to use a generic, perhaps with a constraint
Used reflection to invoke Publish with the right type information
Restructure how you publish things so this isn't an issue any more
The bottom line is you should always publish as the type you want received. Polymorphism in messaging is tricky.
One of the testers in my company found an error on my ASP.Net MVC 3 solution, that I believe is pretty common.
One post to the server it can handle. But if you send a lot of posts, like a denial-of-service attack (DoS attack) it thrown an exception:
Server Error in '/' Application.
Initializing[UseSoft.ProdMaster.Domain.Entities.CustomerOrderHeader#567]-failed to lazily initialize a collection of role: UseSoft.ProdMaster.Domain.Entities.CustomerOrderHeader.CustomerOrderLines, no session or session was closed
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: NHibernate.LazyInitializationException: Initializing[UseSoft.ProdMaster.Domain.Entities.CustomerOrderHeader#567]-failed to lazily initialize a collection of role: UseSoft.ProdMaster.Domain.Entities.CustomerOrderHeader.CustomerOrderLines, no session or session was closed
Source Error:
Line 880:
Line 881:
Line 882: return
Line 883: Json(
Line 884: new
Source File: C:\Projects\DavidPM\Hosts\ProdMaster.Hosts.Web\Areas\Sales\Controllers\CustomerOrderController.cs Line: 882
Stack Trace:
[LazyInitializationException: Initializing[UseSoft.ProdMaster.Domain.Entities.CustomerOrderHeader#567]-failed to lazily initialize a collection of role: UseSoft.ProdMaster.Domain.Entities.CustomerOrderHeader.CustomerOrderLines, no session or session was closed]
NHibernate.Collection.AbstractPersistentCollection.ThrowLazyInitializationException(String message) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Collection\AbstractPersistentCollection.cs:484
NHibernate.Collection.AbstractPersistentCollection.ThrowLazyInitializationExceptionIfNotConnected() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Collection\AbstractPersistentCollection.cs:474
NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean writing) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Collection\AbstractPersistentCollection.cs:465
NHibernate.Collection.AbstractPersistentCollection.Read() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Collection\AbstractPersistentCollection.cs:264
NHibernate.Collection.Generic.PersistentGenericBag`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Collection\Generic\PersistentGenericBag.cs:142
System.Linq.WhereSelectEnumerableIterator`2.MoveNext() +63
System.Linq.Buffer`1..ctor(IEnumerable`1 source) +217
System.Linq.Enumerable.ToArray(IEnumerable`1 source) +78
UseSoft.ProdMaster.Hosts.Web.Areas.Sales.Controllers.CustomerOrderController.SaveOrUpdateOrderLines(CustomerOrderModel customerOrderModel) in C:\Projects\DavidPM\Hosts\ProdMaster.Hosts.Web\Areas\Sales\Controllers\CustomerOrderController.cs:882
lambda_method(Closure , ControllerBase , Object[] ) +162
System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +17
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +208
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +55
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +263
System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +19
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +191
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
System.Web.Mvc.Controller.ExecuteCore() +116
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8862381
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
The is described bellow:
Click in a button many times. Or click once and press enter, it will send a lot of requests to the server "Post".
The Model Basically is:
public class CustomerOrderModel
{
public CustomerOrderModel()
{
this.CustomerOrderLines = new List<CustomerOrderLineModel>();
this.CustomerOrderHeader = new CustomerOrderHeaderModel();
}
public CustomerOrderHeaderModel CustomerOrderHeader
{ get; set; }
public List<CustomerOrderLineModel> CustomerOrderLines
{ get; set; }
}
The Controller:
[HttpPost]
public JsonResult SaveOrUpdateOrderLines( CustomerOrderModel customerOrderModel)
{
if (customerOrderModel.CustomerOrderHeader.OrderNumber == 0)
{
customerOrderModel.CustomerOrderHeader.OrderNumber = this.CustomerOrderService.CreateOrUpdate(customerOrderHeader, false);
}
return
Json(
new
{
OrderNumber = customerOrderModel.CustomerOrderHeader.OrderNumber,
CustomerOrderLines = new
{
CustomerOrderLine = (
from ordeline in custOrderHeader.CustomerOrderLines
select new
{
Id = ordeline.Id,
LineNumber = ordeline.LineNumber,
LineStatus = ordeline.LineStatus
}
).ToArray()
}
},
JsonRequestBehavior.AllowGet
);
}
The CustomerOrder Service:
public class CustomerOrderService : ServiceBase, ICustomerOrderService
{
public long CreateOrUpdate(CustomerOrderHeader customerOrderHeader, bool updateCustomerOrderHeader)
{
using (var session = this.SessionManager.OpenSession())
{
var transaction = session.BeginTransaction();
try
{
//TODO: CHECKS IF PARTNER EXISTS
bool existsPartner = false;
long number = 0;
if (this.PartnerService.FindByName(customerOrderHeader.Partner.Name) != null)
{
existsPartner = true;
}
//CREATE PARTNER AND PARTNER ADDRESSES
if (!existsPartner)
{
this.PartnerService.Create(customerOrderHeader.Partner as Partner);
}
if (!updateCustomerOrderHeader)
{
number = this.CustomerOrderHeaderService.Create(customerOrderHeader, true);
}
else
{
this.CustomerOrderHeaderService.Create(customerOrderHeader, false);
}
transaction.Commit();
return number;
}
catch (Exception ex)
{
transaction.Rollback();
throw ex;
}
finally
{
session.Close();
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomerOrderService"/> class.
/// </summary>
/// <param name="sessionManager">
/// The session manager.
/// </param>
public CustomerOrderService(ISessionManager sessionManager)
{
this.SessionManager = sessionManager;
}
}
The CustomerOrderHeaderService:
public long Create(CustomerOrderHeader customerOrderHeader, bool firstTime)
{
using (var session = this.SessionManager.OpenSession())
{
// var transaction = session.BeginTransaction();
// try
// {
if (firstTime)
{
foreach (var customerOrderLine in customerOrderHeader.CustomerOrderLines)
{
customerOrderLine.CustomerOrderHeader = customerOrderHeader;
}
customerOrderHeader.OrderNumber = this.NextOrderNumber();
session.Save(customerOrderHeader);
}
else
{
CustomerOrderHeader customerOrderHeaderToBeChanged =
this.FindByOrderNumber(customerOrderHeader.OrderNumber);
customerOrderHeaderToBeChanged.CopyDomainProperties(
customerOrderHeader, new[]
{
"Id",
"Partner",
"DataOwner",
"dataOwner",
"SysCreatedOn",
"SysCreatedBy",
"CustomerOrderLines"
});
customerOrderHeaderToBeChanged = UpdateLines(customerOrderHeader, customerOrderHeaderToBeChanged);
List<ICustomerOrderLine> linestoBeInserted = GetTheLineNumbersToBeInserted(customerOrderHeader);
foreach (var customerOrderLine in linestoBeInserted)
{
customerOrderHeaderToBeChanged.CustomerOrderLines.Add(customerOrderLine);
customerOrderLine.CustomerOrderHeader = customerOrderHeaderToBeChanged;
}
session.Update(customerOrderHeaderToBeChanged);
}
// transaction.Commit();
}
// catch (Exception ex)
// {
// transaction.Rollback();
// throw ex;
// }
// finally
// {
// session.Close();
// }
//}
//throw new System.ArgumentException();
return customerOrderHeader.OrderNumber;
}
public class CustomerOrderHeaderService : ServiceBase, ICustomerOrderHeaderService
{
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="CustomerOrderHeaderService"/> class.
/// </summary>
/// <param name="sessionManager">
/// The session manager.
/// </param>
public CustomerOrderHeaderService(ISessionManager sessionManager)
{
this.SessionManager = sessionManager;
}
#endregion
public long Create(CustomerOrderHeader customerOrderHeader, bool firstTime)
{
using (var session = this.SessionManager.OpenSession())
{
// var transaction = session.BeginTransaction();
// try
// {
if (firstTime)
{
foreach (var customerOrderLine in customerOrderHeader.CustomerOrderLines)
{
customerOrderLine.CustomerOrderHeader = customerOrderHeader;
}
customerOrderHeader.OrderNumber = this.NextOrderNumber();
session.Save(customerOrderHeader);
}
else
{
CustomerOrderHeader customerOrderHeaderToBeChanged =
this.FindByOrderNumber(customerOrderHeader.OrderNumber);
customerOrderHeaderToBeChanged.CopyDomainProperties(
customerOrderHeader, new[]
{
"Id",
"Partner",
"DataOwner",
"dataOwner",
"SysCreatedOn",
"SysCreatedBy",
"CustomerOrderLines"
});
customerOrderHeaderToBeChanged = UpdateLines(customerOrderHeader, customerOrderHeaderToBeChanged);
List<ICustomerOrderLine> linestoBeInserted = GetTheLineNumbersToBeInserted(customerOrderHeader);
foreach (var customerOrderLine in linestoBeInserted)
{
customerOrderHeaderToBeChanged.CustomerOrderLines.Add(customerOrderLine);
customerOrderLine.CustomerOrderHeader = customerOrderHeaderToBeChanged;
}
session.Update(customerOrderHeaderToBeChanged);
}
// transaction.Commit();
}
// catch (Exception ex)
// {
// transaction.Rollback();
// throw ex;
// }
// finally
// {
// session.Close();
// }
//}
//throw new System.ArgumentException();
return customerOrderHeader.OrderNumber;
}
}
Increase your connection pool limits can help make it last longer under this, as long as you don't saturate the server/network with too many.
Open and close database connections as late and early (respectively) as possible. You may want to switch your session open/close from per-request (or whatever you are using) to on-use.
In your case, since it's a button on the interface, you can temporarily disable it (with JavaScript) for a period of time so the user can't keep clicking it faster than they should ever need to.
Regardless, you should rate limit the incoming requests either at the network or IIS level.
Make sure that you aren't sharing objects across multiple NHibernate sessions.
That error could occur if Session #1 loads an object that Session #2 tries to commit.
I have checked out the documentation for restsharp and also googled for a solution, but unable to find a solution for this problem:
I have an asp.net mvc3 app and need to connect to quizlet.com to access public flashcards. I have added a reference to restsharp and created the following classes:
public class QuizletObject
{
public string response_type { get; set; }
public int total_results { get; set; }
public int page { get; set; }
public int total_pages { get; set; }
public int sets_with_images { get; set; }
public Set[] sets { get; set; }
}
public class Set
{
public int id { get; set; }
public string title { get; set; }
public string url { get; set; }
public string creator { get; set; }
public int created { get; set; }
public int term_count { get; set; }
public bool has_images { get; set; }
public int last_modified { get; set; }
}
I have a class and method that calls the web service like this:
public class Quizlet
{
private string _baseUrl = "http://api.quizlet.com/1.0/sets";
private const string DevKey = "my dev key";
public QuizletObject GetQuizletSets()
{
_baseUrl = _baseUrl + "?dev_key=" + DevKey + "&q=" + "geography";
RestClient client = new RestClient();
client.BaseUrl = _baseUrl;
RestRequest request = new RestRequest(Method.GET);
request.RequestFormat = DataFormat.Json;
RestResponse<QuizletObject> response = client.Execute<QuizletObject>(request);
return response.Data;
}
}
When I try to access the method using the below code in the view, I get a NullReference exception (Object reference not set to an instance of an object).
#{
QuizletObject quizletObject = quizlet.GetQuizletSets();
}
#foreach (Set quizletSet in quizletObject.sets)
{
#quizletSet.id.ToString()
<br />
#quizletSet.title
<br />
#quizletSet.creator
<br />
#quizletSet.created
<br />
#Html.Encode(quizletSet.url)
<br />
<br />
}
If the same method is executed as below, it returns the complete json object without any problem
RestResponse response = client.Execute(request);
Stack trace:
[NullReferenceException: Object reference not set to an instance of an object.]
ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Users\Girish\Documents\Visual Studio 2010\Projects\RestSharpPoC\RestSharpPoC\Views\Home\Index.cshtml:18
System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +207
System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +81
System.Web.WebPages.StartPage.RunPage() +19
System.Web.WebPages.StartPage.ExecutePageHierarchy() +65
System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +76
System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +220
System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +115
System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) +303
System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +13
System.Web.Mvc.<>c__DisplayClass1c.<InvokeActionResultWithFilters>b__19() +23
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +260
System.Web.Mvc.<>c__DisplayClass1e.<InvokeActionResultWithFilters>b__1b() +19
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +177
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
System.Web.Mvc.Controller.ExecuteCore() +116
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8963149
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
I have seen the same issue. I haven't figured it out in RestSharp, but what I do to work around it is get the json text back then deserialize it myself.
For your code maybe something like this would work:
response = client.Execute(request);
var quizletObject = Newtonsoft.Json.JsonConvert.DeserializeObject<QuizletObject>(response.Content);
As an aside, it looks like you have a problem in your GetQuizletSets() call - each time you call it, it appends _baseUrl to itself so it will continually get longer and longer.
You should keep the base url the same and use request.Resource to specify the parameters, this case it would be:
request.Resource = "?dev_key=" + DevKey + "&q=" + "geography"
If there are multiple available calls you want to make besides "sets" I might set it up like this:
private string _baseUrl = "http://api.quizlet.com/1.0";
...
request.Resource = "sets?dev_key=" + DevKey + "&q=" + "geography";
Edit: Just thought of something else - try using List instead of Set[], maybe RestSharp will have an easier time with that.