I'm trying to create a custom html helper class.
I have the below as a very simple start, complies fine:
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Mobile.HtmlHelpers
{
public static class RequestBox
{
public static HtmlString CascadeBoxFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, object htmlAttributes = null)
{
HtmlString html = (HtmlString)HtmlHelperInputExtensions.TextBoxFor(helper, expression);
return html;
}
// IHtmlContent html = HtmlHelperInputExtensions.TextBoxFor(htmlHelper, expression);
}
}
I am trying to call it and the system doesn't like it..
I can see it if I use:
#foreach (var item in Model.RequestModel.Requests)
{
#RequestBox.CascadeBoxFor(x=>item.EmployeeDescription)
}
But I get:
Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the required formal parameter 'expression' of 'RequestBox.CascadeBoxFor<TModel, TValue>(HtmlHelper, Expression<Func<TModel, TValue>>, object)' Mobile..NET Framework 4.6.1
If I try suggestions of it being an extension and I should be able to use #Html.CascadeBoxFor(x => item.EmployeeDescription), I get:
Severity Code Description Project File Line Suppression State
Error CS1061 'IHtmlHelper<RequestPageModel>' does not contain a definition for 'CascadeBoxFor' and no extension method 'CascadeBoxFor' accepting a first argument of type 'IHtmlHelper<RequestPageModel>' could be found (are you missing a using directive or an assembly reference?) Mobile..NET Framework 4.6.1
Can anyone tell me what is missing here?
Related
I'm just getting to grips with ViewComponents in my Razor pages application.
I have a ViewComponents folder within my project that contains my ViewComponent .cs code:
public class RemoveFromCartViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var result = "123";
return View(result);
}
}
I then have another folder within Pages/Shared/Components called RemoveFromCart. Within this folder I have my default.cshtml
#model string
<h2>
#Model
</h2>
Simply putting the string within a h2 tag.
In my projects Layout.cshtml file I am invoking this ViewComponent:
<div>
#await Component.InvokeAsync("RemoveFromCart")
</div>
When I start my project, the error I get is:
*InvalidOperationException: The view 'Components/RemoveFromCart/123' was not found. The following locations were searched:
/Pages/Components/RemoveFromCart/123.cshtml
/Pages/Shared/Components/RemoveFromCart/123.cshtml
/Views/Shared/Components/RemoveFromCart/123.cshtml*
This is indication my view should be called 123.cshtml which doesnt seem right. What am I doing wrong here? I should simply expect to see the text 123 appear
Thanks
By returning View("123"), you are using this overload:
public ViewViewComponentResult View (string viewName)
Returns a result which will render the partial view with name viewName.
So you are passing the view name, instead of a string value as the view’s model.
You can change that by explicitly calling the View<TModel>(TModel) overload instead:
public IViewComponentResult Invoke()
{
var result = "123";
return View<string>(result);
}
In the long run, I would suggest you to create a model class instead so that you can pass an object instead of just a string. This will avoid having this particular problem and you are also able to easily expand the model contents later on.
I need to add System.Web.Script.Serialization and System.Web.Extensions to my function app so that I can deserialize json string using the following code :
JavaScriptSerializer serializer = new JavaScriptSerializer();
dynamic item = serializer.Deserialize<object>("{ \"test\":\"some data\" }");
string test= item["test"];
This does not work :
#r "System.Web.Script.Serialization"
#r "System.Web.Extensions"
How do I add resolve this issue?
I can't get that work, so I ended up using Newtonsoft Json serializer/deserializer. What you need to do is, follow this instruction to upload project.json file to your function app with this content -
{
"frameworks": {
"net46":{
"dependencies": {
"Newtonsoft.Json": "9.0.1"
}
}
}
}
This basically creates dependency. Then add this name space to your code : "using Newtonsoft.Json.Linq". Voila, you can convert your json string to object like this :
dynamic item = JObject.Parse("{number:1000}");
log.Info($"My number is: {item.number}");
The initial reference likely failed because you were trying to add an assembly reference to System.Web.Script.Serialization, which is a namespace. Adding a reference to System.Web.Extensions should work, but using Json.NET is recommended anyway.
I'm working with the revit api, and one of its problems is that it locks the .dll once the command's run. You have to exit revit before the command can be rebuilt, very time consuming.
After some research, I came across this post on GitHub, that streams the command .dll into memory, thus hiding it from Revit. Letting you rebuild the VS project as much as you like.
The AutoReload Class impliments the revit IExteneralCommand Class which is the link into the Revit Program.
But the AutoReload class hides the actual source DLL from revit. So revit can't lock the DLL and lets one rebuilt the source file.
Only problem is I cant figure out how to implement it, and have revit execute the command. I guess my C# general knowledge is still too limited.
I created an entry in the RevitAddin.addin manifest that points to the AutoReload Method command, but nothing happens.
I've tried to follow all the comments in the posted code, but nothing seems to work; and no luck finding a contact for the developer.
Found at: https://gist.github.com/6084730.git
using System;
namespace Mine
{
// helper class
public class PluginData
{
public DateTime _creation_time;
public Autodesk.Revit.UI.IExternalCommand _instance;
public PluginData(Autodesk.Revit.UI.IExternalCommand instance)
{
_instance = instance;
}
}
//
// Base class for auto-reloading external commands that reside in other dll's
// (that Revit never knows about, and therefore cannot lock)
//
public class AutoReload : Autodesk.Revit.UI.IExternalCommand
{
// keep a static dictionary of loaded modules (so the data persists between calls to Execute)
static System.Collections.Generic.Dictionary<string, PluginData> _dictionary;
String _path; // to the dll
String _class_full_name;
public AutoReload(String path, String class_full_name)
{
if (_dictionary == null)
{
_dictionary = new System.Collections.Generic.Dictionary<string, PluginData>();
}
if (!_dictionary.ContainsKey(class_full_name))
{
PluginData data = new PluginData(null);
_dictionary.Add(class_full_name, data);
}
_path = path;
_class_full_name = class_full_name;
}
public Autodesk.Revit.UI.Result Execute(
Autodesk.Revit.UI.ExternalCommandData commandData,
ref string message,
Autodesk.Revit.DB.ElementSet elements)
{
PluginData data = _dictionary[_class_full_name];
DateTime creation_time = new System.IO.FileInfo(_path).LastWriteTime;
if (creation_time.CompareTo(data._creation_time) > 0)
{
// dll file has been modified, or this is the first time we execute this command.
data._creation_time = creation_time;
byte[] assembly_bytes = System.IO.File.ReadAllBytes(_path);
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(assembly_bytes);
foreach (Type type in assembly.GetTypes())
{
if (type.IsClass && type.FullName == _class_full_name)
{
data._instance = Activator.CreateInstance(type) as Autodesk.Revit.UI.IExternalCommand;
break;
}
}
}
// now actually call the command
return data._instance.Execute(commandData, ref message, elements);
}
}
//
// Derive a class from AutoReload for every auto-reloadable command. Hardcode the path
// to the dll and the full name of the IExternalCommand class in the constructor of the base class.
//
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
public class AutoReloadExample : AutoReload
{
public AutoReloadExample()
: base("C:\\revit2014plugins\\ExampleCommand.dll", "Mine.ExampleCommand")
{
}
}
}
There is an easier approach: Add-in Manager
Go to Revit Developer Center and download the Revit SDK, unzip/install it, the check at \Revit 2016 SDK\Add-In Manager folder. With this tool you can load/reload DLLs without having to modify your code.
There is also some additional information at this blog post.
this is how you can use the above code:
Create a new VS class project; name it anything (eg. AutoLoad)
Copy&Paste the above code in-between the namespace region
reference revitapi.dll & revitapiui.dll
Scroll down to AutoReloadExample class and replace the path to point
your dll
Replace "Mine.ExampleCommand" with your plugins namespace.mainclass
Build the solution
Create an .addin manifest to point this new loader (eg.
AutoLoad.dll)
your .addin should include "FullClassName" AutoLoad.AutoReloadExample
This method uses reflection to create an instance of your plugin and prevent Revit to lock your dll file! You can add more of your commands just by adding new classes like AutoReloadExample and point them with seperate .addin files.
Cheers
Another question on migrating code from v3 to v4:
For v3, I had a customized error reporting, using code like this (in the grammar file):
#members {
public void displayRecognitionError(String[] tokenNames,
RecognitionException e) {
String hdr = getErrorHeader(e);
String msg = getErrorMessage(e, tokenNames);
System.out.println("ERR:"+hdr+":"+msg);
errCount += 1;
}
}
In v4, when compiling the generated java files, I am getting the error:
MyParser.java:163: cannot find symbol
symbol : method getErrorMessage(org.antlr.v4.runtime.RecognitionException,java.lang.String[])
location: class MyParser
String msg = getErrorMessage(e, tokenNames);
^
Is this function replaced by some other function in v4? (I saw some questions and answers on ANTLRErrorListener, but I could not get how to use it for my situation.)
The displayRecognitionError method was removed in ANTLR 4, so even if you correct the body of that method it will not do anything. You need to remove the method from your grammar entirely, and implement ANTLRErrorListener instead. The documentation includes a list of classes that implement the interface, so you can reference those and/or extend one of them to produce the desired functionality.
Once you have an instance of an ANTLRErrorListener, you can use the following code to attach it to a Parser instance.
// remove the default error listener
parser.removeErrorListeners();
// add your custom error listener
parser.addErrorListener(listener);
I have a RazorHelpers.cshtml file in app_code which looks like:
#using Molecular.AdidasCoach.Library.GlobalConstants
#helper Translate(string key)
{
#GlobalConfigs.GetTranslatedValue(key)
}
However, I have a case where I want to use the result as the link text in an #Html.ActionLink(...). I cannot cast the result to a string.
Is there any way to return plain strings from Razor helpers so that I can use them both in HTML and within an #Html helper?
Razor helpers return HelperResult objects.
You can get the raw HTML by calling ToString().
For more information, see my blog post.
I don't think there is a way to make #helper return other types than HelperResult. But you could use a function with a return type of string, e.g.
#functions {
public static string tr(string key) {
return GlobalConfigs.GetTranslatedValue(key);
}
}
then
#Html.ActionLink(tr("KEY"), "action", "controller")
See also http://www.mikesdotnetting.com/article/173/the-difference-between-helpers-and-functions-in-webmatrix
edit: MVC Razor: Helper result in html.actionlink suggests your helper can return a string by using #Html.Raw(GlobalConfigs.GetTranslatedValue(key));
In your case, I think this would also work:
#(GlobalConfigs.GetTranslatedValue(key))
Additional sample:
#helper GetTooltipContent()
{
if(Model.SubCategoryType == SUBCATTYPE.NUMBER_RANGE)
{
#(string.Format("{0} to {1}", Model.SubCategoryMinimum, Model.SubCategoryMaximum))
}
else if(Model.SubCategoryType == SUBCATTYPE.NUMBER_MAXIMUM)
{
#("<= " + Model.SubCategoryMaximum)
}
else if(Model.SubCategoryType == SUBCATTYPE.NUMBER_MINIMUM)
{
#(">= " + Model.SubCategoryMinimum)
}
}
The following statements have been validated against MVC version 5.2.4.0. I am mostly targeting the part with: Is there any way to return plain strings from Razor helpers so that I can use them both in HTML and within an #Html helper?
I did some research on how the built in MVC helpers work and they are actually properties of System.Web.Mvc.WebViewPage class, so they have nothing to do with #helper feature.
Any #helper encodes strings as HTML and works as if the code is copy pasted to the View inside a Razor code block, aka #{ code }. On the other side, #functions are supposed to be used inside Razor blocks.
Well, if a #helper works as if the code is copy pasted, why not use #Html.Raw("<p>cool</p>")? Because the Html property is null inside helpers. Why? I have no idea.
Ok, but we can use a function to return a string and then apply #Html.Raw on the result. Does that work? Yes, it does. The following example creates a <p> element in the DOM:
#functions
{
static string GetString()
{
return "<p>awesome</p>";
}
}
#Html.Raw(GetString())
If you don't understand why #Html.Raw is necessary, please read this fine article from #SLaks about Razor automatic HTML encoding.
What about the approach with the built in properties? Yes, it is possible to create static classes with public methods that work just like that. The only problem is that you have to include the namespace in the View, with the #using keyword. Can that be improved? Yes, by adding the namespace in the Web.config within the Views folder. Example:
Helpers/Global.cs
namespace WebApp.Helpers
{
public static class Global
{
public static IHtmlString GetString()
{
return new HtmlString("Something <b>cool</b>");
}
}
}
Views/Web.config
<?xml version="1.0"?>
<configuration>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<!-- Add to the end of namespaces tag. -->
<add namespace="WebApp.Helpers" />
Usage
#Global.GetString()
What is the outcome? The text Something and an additional <b> element will be found in the DOM. If you need access to the Request, simply add an HttpContextBase parameter to the helper method and pass the WebViewPage.Context property when calling it.
Can it get better? Yes, as always. The same output can be created with #helper and #functions:
#helper GetString1()
{
#(new HtmlString("Something <b>awesome</b>"))
}
#functions {
public static IHtmlString GetString2()
{
return new HtmlString("Something <b>awesome</b>");
}
}
#MyHelper.GetString1()
#MyHelper.GetString2()
Answer
Regarding OP's question, I recommend #Spikolynn's approach to create a function that returns string. However, if you need to write many lines of C# code in the helper, I suggest using a static class helper.