How to render razor view as string in .net core web api? - asp.net-core-2.0

I have .net core web api application. I need to send out the email using razor view. I cannot get razor view as string to send out the email.
I get the exception at Engine.Razor.RunCompile(razorView, "templateKey" + DateTime.Now.Ticks, null, model);
Below is my code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using MyProject.Api.Models;
using RazorEngine;
using RazorEngine.Templating;
namespace MyProject.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AppointmentBookingController : ControllerBase
{
private readonly IHostingEnvironment _hostingEnvironment;
public AppointmentBookingController(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpGet]
[Route("GetViewAsString")]
public string GetViewAsString()
{
string webRootPath = _hostingEnvironment.WebRootPath;
string contentRootPath = _hostingEnvironment.ContentRootPath;
BaseHttpRequestModel model = new BaseHttpRequestModel
{
LanguageID = "ABC"
};
string razorView = System.IO.File.ReadAllText(contentRootPath + #"\Views\EmailTemplates\Test.cshtml");
Engine.Razor.RunCompile(razorView, "templateKey" + DateTime.Now.Ticks, null, model);
return razorView;
}
}
}

You need to supply the model type instead of passing null when using a strongly typed model. You pass null in when using anonymous or dynamic types. Also return the output of RunCompile:
...
var result = Engine.Razor.RunCompile(razorView, "templateKey" + DateTime.Now.Ticks, model.GetType(), model);
return result;

Related

Inference for custom vision model hosted on Azure Cognitive Services stopped working in December for me

I have a service that has been successfully performing inferences for 2 years, but API stopped working in December. I have created a simple App based on documentation from Microsoft to reproduce the problem. Please see code below.
Is anybody else experience this problem?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction;
using static System.Net.Mime.MediaTypeNames;
namespace TestCustomVision
{
class Program
{
public static void Main()
{
string imageFilePath = <My Image>;
MakePredictionRequest(imageFilePath).Wait();
Console.WriteLine("\n\nHit ENTER to exit...");
Console.ReadLine();
}
public static async Task MakePredictionRequest(string imageFilePath)
{
var client = new HttpClient();
// Request headers - replace this example key with your valid Prediction-Key.
client.DefaultRequestHeaders.Add("Prediction-Key", <My key>);
// Prediction URL - replace this example URL with your valid Prediction URL.
string url = <Prediction URL>;
HttpResponseMessage response;
// Request body. Try this sample with a locally stored image.
byte[] byteData = GetImageAsByteArray(imageFilePath);
using (var content = new ByteArrayContent(byteData))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response = await client.PostAsync(url, content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
private static byte[] GetImageAsByteArray(string imageFilePath)
{
FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
BinaryReader binaryReader = new BinaryReader(fileStream);
return binaryReader.ReadBytes((int)fileStream.Length);
}
}
}

ServiceStack .Net Core fluent validation Not consistent with full .NET 4.6.2

So we have a working ServiceStack service hosted inside a Windows Service using .Net 4.6.2, which uses a bunch of Fluent Validation validators.
We would like to port this to .Net Core. So I started to create cut down project just with a few of the features of our main app to see what the port to .Net Core would be like.
Most things are fine, such as
IOC
Routing
Hosting
Endpoint is working
The thing that does not seem to be correct is validation. To illustrate this I will walk through some existing .Net 4.6.2 code and then the .Net Core code. Where I have included the results for both
.NET 4.6.2 example
This is all good when using the full .Net 4.6.2 framework and the various ServiceStack Nuget packages.
For example I have this basic Dto (please ignore the strange name, long story not my choice)
using ServiceStack;
namespace .RiskStore.ApiModel.Analysis
{
[Route("/analysis/run", "POST")]
public class AnalysisRunRequest : BaseRequest, IReturn<AnalysisRunResponse>
{
public AnalysisPart Analysis { get; set; }
}
}
Where we have this base class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace .RiskStore.ApiModel
{
public abstract class BaseRequest
{
public string CreatedBy { get; set;}
}
}
And we have this validator (we have way more than this working in .Net 4.6.2 app, this is just to show differences between full .Net and .Net Core which we will see in a minute)
using .RiskStore.ApiModel.Analysis;
using ServiceStack.FluentValidation;
namespace .RiskStore.ApiServer.Validators.Analysis
{
public class AnalysisRunRequestValidator : AbstractValidator<AnalysisRunRequest>
{
public AnalysisRunRequestValidator(AnalysisPartValidator analysisPartValidator)
{
RuleFor(analysis => analysis.CreatedBy)
.Must(HaveGoodCreatedBy)
.WithMessage("CreatedBy MUST be 'sbarber'")
.WithErrorCode(ErrorCodes.ValidationErrorCode);
}
private bool HaveGoodCreatedBy(AnalysisRunRequest analysisRunRequest, string createdBy)
{
return createdBy == "sbarber";
}
}
}
And here is my host file for this service
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Funq;
using .RiskStore.ApiModel.Analysis;
using .RiskStore.ApiModel.Analysis.Results;
using .RiskStore.ApiServer.Api.Analysis;
using .RiskStore.ApiServer.Exceptions;
using .RiskStore.ApiServer.IOC;
using .RiskStore.ApiServer.Services;
using .RiskStore.ApiServer.Services.Results;
using .RiskStore.ApiServer.Validators.Analysis;
using .RiskStore.ApiServer.Validators.Analysis.Results;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Results;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Search;
using .RiskStore.DataAccess.AnalysisRun.Repositories.Validation;
using .RiskStore.DataAccess.Configuration;
using .RiskStore.DataAccess.Connectivity;
using .RiskStore.DataAccess.Ingestion.Repositories.EventSet;
using .RiskStore.DataAccess.JobLog.Repositories;
using .RiskStore.DataAccess.StaticData.Repositories;
using .RiskStore.DataAccess.UnitOfWork;
using .RiskStore.Toolkit.Configuration;
using .RiskStore.Toolkit.Jobs.Repositories;
using .RiskStore.Toolkit.Storage;
using .RiskStore.Toolkit.Utils;
using .Toolkit;
using .Toolkit.Configuration;
using .Toolkit.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ServiceStack;
using ServiceStack.Text;
using ServiceStack.Validation;
namespace .RiskStore.ApiServer.Api
{
public class ApiServerHttpHost : AppHostHttpListenerBase
{
private readonly ILogger _log = Log.ForContext<ApiServerHttpHost>();
public static string RoutePrefix => "analysisapi";
/// <summary>
/// Base constructor requires a Name and Assembly where web service implementation is located
/// </summary>
public ApiServerHttpHost()
: base(typeof(ApiServerHttpHost).FullName, typeof(AnalysisServiceStackService).Assembly)
{
_log.Debug("ApiServerHttpHost constructed");
}
public override void SetConfig(HostConfig config)
{
base.SetConfig(config);
JsConfig.TreatEnumAsInteger = true;
JsConfig.EmitCamelCaseNames = true;
JsConfig.IncludeNullValues = true;
JsConfig.AlwaysUseUtc = true;
JsConfig<Guid>.SerializeFn = guid => guid.ToString();
JsConfig<Guid>.DeSerializeFn = Guid.Parse;
config.HandlerFactoryPath = RoutePrefix;
var exceptionMappings = new Dictionary<Type, int>
{
{typeof(JobServiceException), 400},
{typeof(NullReferenceException), 400},
};
config.MapExceptionToStatusCode = exceptionMappings;
_log.Debug("ApiServerHttpHost SetConfig ok");
}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
public override void Configure(Container container)
{
//Config examples
//this.Plugins.Add(new PostmanFeature());
//this.Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(AnalysisRunRequestValidator).Assembly);
.......
.......
.......
.......
container.Register<AnalysisPartValidator>(c => new AnalysisPartValidator(
c.Resolve<AnalysisDealPartLinkingModelEventSetValidator>(),
c.Resolve<AnalysisOutputSettingsPartValidMetaRisksValidator>(),
c.Resolve<AnalysisOutputSettingsGroupPartValidMetaRisksValidator>(),
c.Resolve<AnalysisDealPartCollectionValidator>(),
c.Resolve<AnalysisPortfolioPartCollectionValidator>(),
c.Resolve<UniqueCombinedOutputSettingsPropertiesValidator>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisRunRequestValidator>(c => new AnalysisRunRequestValidator(c.Resolve<AnalysisPartValidator>()))
.ReusedWithin(ReuseScope.None);
_log.Debug("ApiServerHttpHost Configure ok");
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json
});
}}
}
So I then hit this endpoint with this JSON
{
"Analysis": {
//Not important for discussion
//Not important for discussion
//Not important for discussion
//Not important for discussion
},
"CreatedBy": "frank"
}
And I get this response in PostMan tool (which I was expecting)
So that's all good.
.NET Core example
So now lets see what its like in .Net core example.
Lets start with the request dto
using System;
namespace ServiceStack.Demo.Model.Core
{
[Route("/analysis/run", "POST")]
public class AnalysisRunRequest : BaseRequest, IReturn<AnalysisRunResponse>
{
public AnalysisDto Analysis { get; set; }
}
}
Which uses this base request object
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public abstract class BaseRequest
{
public string CreatedBy { get; set; }
}
}
And here is the same validator we used from .NET 4.6.2 example, but in my .Net Core code instead
using System;
using System.Collections.Generic;
using System.Text;
using ServiceStack.Demo.Model.Core;
using ServiceStack.FluentValidation;
namespace ServiceStack.Demo.Core.Validators
{
public class AnalysisRunRequestValidator : AbstractValidator<AnalysisRunRequest>
{
public AnalysisRunRequestValidator(AnalysisDtoValidator analysisDtoValidator)
{
RuleFor(analysis => analysis.CreatedBy)
.Must(HaveGoodCreatedBy)
.WithMessage("CreatedBy MUST be 'sbarber'")
.WithErrorCode(ErrorCodes.ValidationErrorCode);
}
private bool HaveGoodCreatedBy(AnalysisRunRequest analysisRunRequest, string createdBy)
{
return createdBy == "sbarber";
}
}
}
And here is my host code for .Net core example
using System;
using System.Collections.Generic;
using System.Text;
using Funq;
using ServiceStack.Demo.Core.Api.Analysis;
using ServiceStack.Demo.Core.IOC;
using ServiceStack.Demo.Core.Services;
using ServiceStack.Demo.Core.Validators;
using ServiceStack.Text;
using ServiceStack.Validation;
namespace ServiceStack.Demo.Core.Api
{
public class ApiServerHttpHost : AppHostBase
{
public static string RoutePrefix => "analysisapi";
public ApiServerHttpHost()
: base(typeof(ApiServerHttpHost).FullName, typeof(AnalysisServiceStackService).GetAssembly())
{
Console.WriteLine("ApiServerHttpHost constructed");
}
public override void SetConfig(HostConfig config)
{
base.SetConfig(config);
JsConfig.TreatEnumAsInteger = true;
JsConfig.EmitCamelCaseNames = true;
JsConfig.IncludeNullValues = true;
JsConfig.AlwaysUseUtc = true;
JsConfig<Guid>.SerializeFn = guid => guid.ToString();
JsConfig<Guid>.DeSerializeFn = Guid.Parse;
config.HandlerFactoryPath = RoutePrefix;
var exceptionMappings = new Dictionary<Type, int>
{
{typeof(NullReferenceException), 400},
};
config.MapExceptionToStatusCode = exceptionMappings;
Console.WriteLine("ApiServerHttpHost SetConfig ok");
}
public override void Configure(Container container)
{
//Config examples
//this.Plugins.Add(new PostmanFeature());
//this.Plugins.Add(new CorsFeature());
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(ApiServerHttpHost).GetAssembly());
container.RegisterAutoWiredAs<DateProvider, IDateProvider>()
.ReusedWithin(ReuseScope.Container);
container.RegisterAutoWiredAs<FakeRepository, IFakeRepository>()
.ReusedWithin(ReuseScope.Container);
container.Register<LifetimeScopeManager>(cont => new LifetimeScopeManager(cont))
.ReusedWithin(ReuseScope.Hierarchy);
container.Register<DummySettingsPropertiesValidator>(c => new DummySettingsPropertiesValidator(c.Resolve<LifetimeScopeManager>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisDtoValidator>(c => new AnalysisDtoValidator(
c.Resolve<DummySettingsPropertiesValidator>()))
.ReusedWithin(ReuseScope.None);
container.Register<AnalysisRunRequestValidator>(c => new AnalysisRunRequestValidator(c.Resolve<AnalysisDtoValidator>()))
.ReusedWithin(ReuseScope.None);
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json
});
}
}
}
And this is me trying to now hit the .Net Core endpoint with the same bad JSON payload as demonstrated above with the .Net 4.6.2 example, which gave correct Http response (i.e included error that I was expecting in response)
Anyway here is payload being sent to .Net Core endpoint
{
"Analysis": {
//Not important for discussion
//Not important for discussion
//Not important for discussion
//Not important for discussion
},
"CreatedBy": "frank"
}
Where we can see that we are getting into the .Net Core example validator code just fine
But this time I get a very different Http response (one that I was not expecting at all). I get this
It can be seen that we do indeed get the correct Status code of "400" (failed) which is good. But we DO NOT get anything about the validation failure at all.
I was expecting this to give me the same http response as the original .Net 4.6.2 example above.
But what I seem to be getting back is the JSON representing the AnalysisRunResponse. Which looks like this
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public class AnalysisRunResponse : BaseResponse
{
public Guid AnalysisUid { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceStack.Demo.Model.Core
{
public abstract class BaseResponse
{
public ResponseStatus ResponseStatus { get; set; }
}
}
I thought the way ServiceStack works (in fact that is how it works for ALL our existing .Net 4.6.2 code) is that the validation is done first, if AND ONLY if it passes validation is the actual route code run
But this .Net core example seems to not work like that.
I have a break point set in Visual Studio for the actual route and Console.WriteLine(..) but that is never hit and I never see the result of the Console.WriteLine(..)
What am I doing wrong?
This doesn't seem to any longer an issue with the latest v5 of ServiceStack that's now available on MyGet.
As sending this request:
Is returning the expected response:
The .NET Core packages are merged with the main packages in ServiceStack v5 so you'll need to remove the .Core prefix to download them, i.e:
<PackageReference Include="ServiceStack" Version="5.*" />
<PackageReference Include="ServiceStack.Server" Version="5.*" />
You'll also need to add ServiceStack's MyGet feed to fetch the latest ServiceStack v5 NuGet packages which you can do by adding this NuGet.config in the same folder as your .sln:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="ServiceStack MyGet feed" value="https://www.myget.org/F/servicestack" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

mvc genericly binding byte[] properties

I've tried using a custom model binder but my request.files is not populated. IN forms collection, the input of type file for the byte[] property is populated by file name only.
<input id="collection[#index].#p.Name" name="collection[#index].#p.Name" type="file" />
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace male.services.mvc.Binders
{
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.GetProperties().Any(o => o.PropertyType == typeof(byte[])))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var pi in bindingContext.ModelType.GetProperties().Where(o => o.PropertyType == typeof(byte[])))
{
// can't access any property in the parameters that gives me my file input or my stream
}
return base.BindModel(controllerContext, bindingContext);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
}
I found the answer. This will allow me to go straight from html file inputs to EF models without resorting to hand-crafting a file argument for each model type and property type which needs it. (Aint nobody got time for that)
In order for a form to post files, it must have the enctype attribute:
<form enctype="multipart/form-data" ... />
Once that is done, the file can be accessed via controllerContext.HttpContext.Request.Files
UPDATE - Working Example
Note: You don't need the Regex stuff if you aren't trying to bind a collection. You also don't need the EndsWith, you can just use ==
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
namespace male.services.mvc.Binders
{
public class CustomModelBinder : DefaultModelBinder
{
HttpRequestBase request;
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
request = controllerContext.HttpContext.Request;
return base.BindModel(controllerContext, bindingContext);
}
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(byte[]))
{
var key = request.Files.AllKeys.SingleOrDefault(o => o.EndsWith($"].{propertyDescriptor.Name}"));
var fileIndex = Regex.Match(key, #"\[(.*)\]").Groups[1].Value;
var modelIndex = Regex.Match(bindingContext.ModelName, #"\[(.*)\]").Groups[1].Value;
if(fileIndex == modelIndex)
{
HttpPostedFileWrapper httpPostedFile = (HttpPostedFileWrapper)request.Files[request.Files.AllKeys.SingleOrDefault(o => o.EndsWith(propertyDescriptor.Name))];
using (MemoryStream ms = new MemoryStream())
{
httpPostedFile.InputStream.CopyTo(ms);
propertyDescriptor.SetValue(bindingContext.Model, ms.ToArray());
}
}
}
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}

MVC 5 Custom Route - Error 404 in LegacyRoute : RouteBase (Example from the book Pro ASP.NET MVC 5 from Apress in chapter 16)

The code provided in the book Pro ASP.NET MVC 5 from Apress in chapter 16 (Routing Incoming URLs) The example is about legacy urls. Next i will put the code samples for the custom route, routeconfig, controller and view.
LegacyRoute.cs
using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes.Infrastructure
{
public class LegacyRoute : RouteBase
{
private string[] urls;
public LegacyRoute(params string[] targetUrls)
{
urls = targetUrls;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
{
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "Legacy");
result.Values.Add("action", "GetLegacyURL");
result.Values.Add("legacyURL", requestedURL);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null;
if (values.ContainsKey("legacyURL") && urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase))
{
result = new VirtualPathData(this, new UrlHelper(requestContext).Content((string)values["legacyURL"]).Substring(1));
}
return result;
}
}
}
RouteConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Mvc.Routing.Constraints;
using UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
//routes.MapRoute("NewRoute", "App/Do{action}", new { controller = "Home", id = UrlParameter.Optional });
//routes.Add(new Route("SayHello", new CustomRouteHandler()));
routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
}
}
}
LegacyController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace UrlsAndRoutes.Controllers
{
public class LegacyController : Controller
{
public ActionResult GetLegacyURL(string legacyURL)
{
return View((object)legacyURL);
}
}
}
GetLegacyURL.cshtml
#model string
#{
ViewBag.Title = "GetLegacyURL";
Layout = null;
}
<h2>GetLegacyURL</h2>
The URL requested was: #Model
I can't figure how to put it work. I always get 404 error (http://my.machine/articles/Windows_3.1_Overview.html). The method in the GetRouteData in LegacyRoute.cs is never called. If I remove the . from the URL the code works fine. Can anyone give some advice or help?
It is strange that no one are complaining that the code doesn't work.
Page 445 provides the solution. You need to edit IIS Express settings.
Right click on the IIS Express icon on the taskbar while the application is running -> Show all applications -> Click on the site you want to configure -> Click on the configuration file
Search for System.Web.Routing.UrlRoutingModule
Remove the preCondition attribute value so the line becomes <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
Restart the application and you are ready.
routes.MapMvcAttributeRoutes();
routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html/",
"~/old/.NET_1.0_Class_Library/"));
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home" ,action="index" });
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
Try with above code , this should be worked.
Malinda Sanjaka

Sharepoint Webpart Properties / Rich text box?

Is it possible to make a String in a web part properties editable with a rich text box (to be able to use the Bold, etc.) ?
UPDATE / SOLUTION
The 1st class is the "Custom property" that should appear in the toolbar
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
namespace MyCustomProperty
{
public class RichTextToolbarProperty : Microsoft.SharePoint.WebPartPages.ToolPart
{
InputFormTextBox textBox;
Panel toolPartPanel;
protected override void CreateChildControls()
{
toolPartPanel = new Panel();
toolPartPanel.GroupingText = "Default text here";
textBox = new InputFormTextBox();
textBox.TextMode = TextBoxMode.MultiLine;
textBox.Rows = 10;
textBox.RichText = true;
textBox.RichTextMode = SPRichTextMode.FullHtml;
BasePublicationWebPart wp = (BasePublicationWebPart)this.ParentToolPane.SelectedWebPart;
textBox.Text = wp.DefaultText;
toolPartPanel.Controls.Add(textBox);
Controls.Add(toolPartPanel);
base.CreateChildControls();
}
public override void ApplyChanges()
{
BasePublicationWebPart wp = (BasePublicationWebPart)this.ParentToolPane.SelectedWebPart;
wp.DefaultText = textBox.Text;
}
}
}
The 2nd class is the WebPart :
using System;
using System.Data;
using System.Text;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.WebControls;
using System.ComponentModel;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
namespace MyWebPart
{
public abstract class BasePublicationWebPart : Microsoft.SharePoint.WebPartPages.WebPart
{
public string DefaultText
{
get
{
return _defaultText;
}
set { _defaultText = value; }
}
public override ToolPart[] GetToolParts()
{
ToolPart[] allToolParts = new ToolPart[3];
WebPartToolPart standardToolParts = new WebPartToolPart();
CustomPropertyToolPart customToolParts = new CustomPropertyToolPart();
allToolParts[0] = standardToolParts;
allToolParts[1] = customToolParts;
allToolParts[2] = new MyCustomProperty.RichTextToolbarProperty();
return allToolParts;
}
// ... some usual web part code should go here ... ///
Yes it is, you might want to inspect, how the "Custom Content Editor Web Part" is built: http://www.codeproject.com/KB/sharepoint/Custom_CEWP_4_SharePoint.aspx

Resources